Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement Token Expiry Check and Refresh using Refresh Tokens #310

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

krezovic
Copy link
Contributor

@krezovic krezovic commented May 1, 2024

DO NOT MERGE. Initial RFC for approach.

Testing done

Submitter checklist

Edit tasklist title
Beta Give feedback Tasklist Submitter checklist, more options

Delete tasklist

Delete tasklist block?
Are you sure? All relationships in this tasklist will be removed.
  1. Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
    Options
  2. Ensure that the pull request title represents the desired changelog entry
    Options
  3. Please describe what you did
    Options
  4. Link to relevant issues in GitHub or Jira
    Options
  5. Link to relevant pull requests, esp. upstream and downstream changes
    Options
  6. Ensure you have provided tests - that demonstrates feature works or fixes the issue
    Options

@krezovic krezovic requested a review from a team as a code owner May 1, 2024 19:56
pom.xml Outdated Show resolved Hide resolved
public class OicCredentials {
private String accessToken;
private IdToken idToken;
private String refreshToken;

Check warning

Code scanning / Jenkins Security Scan

Jenkins: Plaintext password storage

Field should be reviewed whether it stores a password and is serialized to disk: refreshToken
import java.time.Clock;

public class OicCredentials {
private String accessToken;

Check warning

Code scanning / Jenkins Security Scan

Jenkins: Plaintext password storage

Field should be reviewed whether it stores a password and is serialized to disk: accessToken
@michael-doubez michael-doubez linked an issue May 1, 2024 that may be closed by this pull request
@michael-doubez michael-doubez added enhancement draft Work in progress labels May 1, 2024
@krezovic krezovic force-pushed the token_refresh branch 3 times, most recently from 58a8807 to e737987 Compare May 1, 2024 22:31
Copy link

codecov bot commented May 1, 2024

Codecov Report

Attention: Patch coverage is 33.00000% with 67 lines in your changes are missing coverage. Please review.

Project coverage is 69.18%. Comparing base (e327bf2) to head (1a5e4f2).
Report is 7 commits behind head on master.

Files Patch % Lines
...va/org/jenkinsci/plugins/oic/OicSecurityRealm.java 28.94% 51 Missing and 3 partials ⚠️
...java/org/jenkinsci/plugins/oic/OicCredentials.java 50.00% 7 Missing and 3 partials ⚠️
...gins/oic/WellKnownOpenIDConfigurationResponse.java 33.33% 1 Missing and 1 partial ⚠️
...ain/java/org/jenkinsci/plugins/oic/OicSession.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #310      +/-   ##
============================================
- Coverage     73.35%   69.18%   -4.18%     
- Complexity      211      220       +9     
============================================
  Files            10       11       +1     
  Lines           882      980      +98     
  Branches        124      138      +14     
============================================
+ Hits            647      678      +31     
- Misses          172      232      +60     
- Partials         63       70       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@krezovic
Copy link
Contributor Author

krezovic commented May 1, 2024

Hi @michael-doubez, this is a working draft where the most basic and common functionality works:

  • Added "Enable/disable refresh token" support - a simple checkbox when "manual config", detected when "automatic config" by querying "supproted_grant_types" discovery response attribute
  • Added "strict" mode (disabled by default) that indicates whether an expired access token and missing refresh token, or simply failed refresh request (due to various reasons) will result in "401" or we simply ignore it
  • Added configurable clock skew that prolongs lifetime of an access token for a pre-configured amount of seconds to adjust for any time sync issues. Defaults to 60 seconds
  • Any updated roles/groups are reflected on every token refresh, as they should be

Now, before I move on to update documentation and write some tests, I'd like to get some feedback if the approach is correct. In addition, I have additional points that I have noticed but not sure if I should handle or how to handle at all.

  • First and most obvious point: In the build pipeline there is a warning that "accessToken" and "refreshToken" look sensitive and may be stored in plain text files. I have no idea where the SecurityContext is stored, so do you think we should mark the tokens as credentials?
  • There is a concurrency issue where two parallel requests will trigger two token refresh requests at the same time. I am unable to find a good place for a synchronization object that does not involve a manual clean up. Should I take care about this? If yes, do you happen to know if there's any place that I could place a synchronization primitive such as timestamp of first request that triggered the refresh, and to let any subsequent parallel requests just skip the refresh as it's "being handled"? Is there any "self-cleaning" cache used elsewhere in the project that I could hook into?

Just for fun, without any activity I keep my jenkins tab opened and UI triggers a lot of requests every x seconds, resulting in at least 2 token refresh requests without any activity

image

  • When a Token is expired, no refresh can be performed, and strict mode is on, should we simply redirect the user to the logout URL or do we clean up the session manually and simply "halt"?

Any additional feedback is welcome.

@michael-doubez
Copy link
Contributor

Hello,

Thanks for the work. You can ignore the warnings about secret leaking, they are mostly irrelevant.
I'll check the concurrency issue but if the information is local to a user context, there shouldn't be any issue.

There are a lot of features in the PR. I would break them down into:

  • handle session timeout: if refresh/offline token is unavailable or expired, how to clear session information and request re-login
  • handle additional (refresh) token lifetime : how to request it if available, as you did. It should be store in session information such as to be available globally, it may also be revoked at logout because lifetime is typically longer than access token
  • handle refresh of user info

I should be able to review in depth and (hopefuly) provide relevant feedback next Sunday.

@krezovic
Copy link
Contributor Author

krezovic commented May 2, 2024

Hello,

I believe I have handled most of the use-cases except the logout. The question remains on how do we proceed.

Answering the points in the order they were raised

  • Easiest way to do this is to request log out at IdP. This is an already supported feature. This will invalidate the refresh token, and next refresh will fail. This use-case has been handled and works without clearing the Jenkins related session stuff (as of yet - we have to agree on how to proceed)
  • Refresh tokens may be extended indefinitely as long as the session is active on the IdP. I wouldn't go beyond what was done here as described in step 1 - refresh token returns "invalid_grant" -> assume RT is expired or revoked -> destroy the user session. User may be logged elsewhere out of IdP and detecting this is a bit more bothersome than I'd like to go into - but there are standards on how to handle this scenario (see below)
  • Refresh of user info and ID token has already been incorporated in the MR and I re-used the existing userinfo request code and IdToken decoding (without the validations - but that can be re-used if needed, as well)

Back to the first point, we can implement additional features if needed, namely

@michael-doubez
Copy link
Contributor

@krezovic the change looks good to me.

What bothers me most is the idtoken kept in the session information. It should be removed and kept only in the credentials but that can be ironed out later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
draft Work in progress enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OIDC logins are cached forever
2 participants