fix: added policies to RefreshToken DB#950
Conversation
|
@aldbr I have a question: Is it normal that none of the policies ( diracx/diracx-routers/src/diracx/routers/access_policies.py Lines 93 to 104 in ebce8ad The only one having it is Since all the work for adding policies to token is done here: diracx/diracx-routers/src/diracx/routers/auth/token.py Lines 56 to 76 in ebce8ad |
19d3bbc to
6382a3f
Compare
Yes, this is there as something that we know we will need but that we don't yet have a use for. |
aldbr
left a comment
There was a problem hiding this comment.
Can you also design a migration plan for the installation already in production please (and make sure the code is able to handle that)? 🙂
Thanks!
| def enrich_tokens( | ||
| access_payload: AccessTokenPayload, refresh_payload: RefreshTokenPayload | None | ||
| ) -> tuple[dict, dict]: | ||
| def enrich_tokens() -> tuple[dict, dict]: |
There was a problem hiding this comment.
Any reason for modifying the enrich_tokens method?
There was a problem hiding this comment.
These changes were made due to my comment here: #950 (comment)
I wasn't sure access_payload and refresh_payload had an impact or not since there was no real implementation of this method.
Also, if we need the payload to enrich the token, how can we do that? Because currently, the payload is created after we insert the token in the DB, and so, after we enrich it too:
# Enrich the token with policy specific content
dirac_access_policies = {}
dirac_refresh_policies = {}
for policy_name, policy in policies.items():
access_extra, refresh_extra = policy.enrich_tokens() # --> with my refactor, token is enriched first here
if access_extra:
dirac_access_policies[policy_name] = access_extra
if refresh_extra:
dirac_refresh_policies[policy_name] = refresh_extra
refresh_payload: RefreshTokenPayload | None = None
if include_refresh_token:
# Insert the refresh token with user details into the RefreshTokens table
# User details are needed to regenerate access tokens later
refresh_jti = await insert_refresh_token(
auth_db=auth_db, subject=sub, scope=scope, policies=dirac_refresh_policies
) # --> we need to retrieve the JTI here
# Generate refresh token payload
if refresh_token_expire_minutes is None:
refresh_token_expire_minutes = settings.refresh_token_expire_minutes
refresh_exp = uuid7_to_datetime(refresh_jti) + timedelta(
minutes=refresh_token_expire_minutes
)
refresh_payload = RefreshTokenPayload(
jti=str(refresh_jti),
exp=refresh_exp,
# legacy_exchange is used to indicate that the original refresh token
# was obtained from the legacy_exchange endpoint
legacy_exchange=legacy_exchange,
dirac_policies=dirac_refresh_policies,
) # --> the payload is only created hereMaybe one way to do that would be to create an "update_token_policies" function and call it at the end, so it would be:
- create refresh token in DB
- create refresh and access tokens payloads
- enrich them
- update refresh token and access payloads dirac_policies
- update refresh token policies in DB with function
What do you think? Do you have any other idea?
| # Enrich the token with policy specific content | ||
| dirac_access_policies = {} | ||
| dirac_refresh_policies = {} | ||
| for policy_name, policy in policies.items(): | ||
| access_extra, refresh_extra = policy.enrich_tokens() | ||
| if access_extra: | ||
| dirac_access_policies[policy_name] = access_extra | ||
| if refresh_extra: | ||
| dirac_refresh_policies[policy_name] = refresh_extra |
There was a problem hiding this comment.
When you renew the refresh token, you don't reuse the policies that you initially stored in the DB, is that expected? (then what's the point in storing the policies in the DB?)
What if the policies changed in the meantime? Then you get a different refresh token.
Please test this use case if possible
There was a problem hiding this comment.
When you renew the refresh token, you don't reuse the policies that you initially stored in the DB, is that expected? (then what's the point in storing the policies in the DB?)
I think so, no? The issue was asking to add the policies to the DB, since they were only JWT-side, it's true that I don't see the point in storing them in DB if we don't reuse them but I also don't see a point of reusing them if the refresh token will always be re-created by calling exchange_token.
What if the policies changed in the meantime? Then you get a different refresh token.
Since we don't reuse the policies from the DB and the token is re-created anyway, you'll have a new token with the new policies?
| "Status", RefreshTokenStatus, server_default=RefreshTokenStatus.CREATED.name | ||
| ) | ||
| scope: Mapped[str1024] = mapped_column("Scope") | ||
| policies: Mapped[dict[str, Any]] = mapped_column("Policies") |
There was a problem hiding this comment.
I think it should be null by default, at least for the migration (because old refresh tokens don't have Policies. Also it would probably by null permanently, am I wrong?
There was a problem hiding this comment.
I agree about the null by default and I think it's automatically assigned for older values when you add the column in the DB with ALTER TABLE RefreshTokens ADD COLUMN Policies JSON; (sql-alchemy only auto-check for new tables, not new columns, I don't know if there's a setting for that), at least, it did it for my older tokens. I will re-check that if you want and add a default anyway.
I don't understand that:
Also it would probably by null permanently, am I wrong?
As I mentionned in the PR, the policies are stored correctly in the DB, it's not null:
made with a temporary change in
BaseAccessPolicy.enrich_tokento return{}, {"TemporaryRefreshPolicy"}
- In the DB,
RefreshTokentable:
| 019ed007e31a7ec0980b77d7bf880d28 | CREATED | vo:diracAdmin | diracAdmin:CiRBRkFFNjIzOS0wRkIzLTRFQUEtQUJGRC0zQ0MxRDQ1MzJBNDESBWxvY2Fs | {"wms": {"PolicySpecific": "TemporaryRefreshPolicy"}, "sandbox": {"PolicySpecific": "TemporaryRefreshPolicy"}} |
If you're talking about this case: #950 (comment), I will take a look at it.
Closes: #524
Issue found:
Policies were added by
mint_tokenafterinsert_token(in DB) beforeexchange_tokenwas called, so they were never added to the DB, only to the JWT.RefreshTokensDB was also missing a Policy column.Changes:
RefreshTokensnow has apoliciescolumn (updates to the DB tablecolumns might be needed)exchange_tokenfunction.mint_tokenfunction doesn't need to enrich the token anymore since the payloads already have the policies inside them, fromexchange_tokenfunction.enrich_tokenfunction (?). I'm not sure if they are really unnecessary -> see about the lack of usage ofenrich_token, : fix: added policies to RefreshToken DB #950 (comment)Results:
RefreshTokentable: