#10227: RelayState max length not respected (fix)#10231
#10227: RelayState max length not respected (fix)#10231evtr wants to merge 1 commit intokeycloak:mainfrom evtr:main
Conversation
|
@mposolda: Please, look at this :-) |
|
If you need more info on this PR, please let me know |
|
@ahus1: Are there any chances for this pr to be accepted? |
|
Hi @evtr - thanks for reaching out. I did some labeling on this PR as it addresses SAML. I'm no expert in SAML, therefore I don't feel confident enough to review it. Looking at the code it technically looks ok. I also see that you opened an issue before creating the PR, this is great. The commit message deviates a little bit from the contributing guidelines. This is no blocker, maybe you could still update it and then force-push it again so that it follows the format: As this is a community pull request, you could reach out to SAML experts in the community to have an opinion on this PR. This could include confirming that it aligns with the specification, and if people think it might break existing implementations. Please use your usual Keycloak channels including the keycloak-dev mailing list to find those community members that are interested in SAML. I also see that two people in the issue reacted with a "thumbs up", maybe they would volunteer for a review. Once you found those people, please keep the discussion in this GitHub issue, so that we have it "on record" for a final approval. The final approval and decision to merge it will be with a maintainer (I'm not a maintainer). Please let me know if you find this approach / comment helpful. |
ahus1
left a comment
There was a problem hiding this comment.
I did a technical code review for the unit test and got the following questions. This is no review of the SAML code itself, as I don't have the necessary SAML knowledge.
| String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk"; | ||
| String tabId = "vpISZLVDAc0"; | ||
| String clientId = "https://login.blablabla.com/auth/realms/broker"; | ||
| String encodedState = IdentityBrokerState.decoded(state, clientId, tabId).getEncoded(); |
There was a problem hiding this comment.
As the "arrange" part is calling IdentityBrokerState, thereby depending on the tested class. There is also no test to verify the output of encodedState.
With that observation, I'd rather have the encodedState as a hardcoded string in here, so that it is visible in the code what is tested as the input, and that input doesn't change unexpectedly wehn decoded() or getEncoded() change.
| String encodedState = IdentityBrokerState.decoded(state, clientId, tabId).getEncoded(); | |
| String encodedState = IdentityBrokerState.decoded(state, clientId, tabId).getEncoded(); | |
| assertEquals("gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.1997353014", encodedState); |
| String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk"; | ||
| String tabId = "vpISZLVDAc0"; | ||
| String clientId = "foo"; | ||
| String encodedState = IdentityBrokerState.decoded(state, clientId, tabId).getEncoded(); |
There was a problem hiding this comment.
same as above.
| String encodedState = IdentityBrokerState.decoded(state, clientId, tabId).getEncoded(); | |
| String encodedState = IdentityBrokerState.decoded(state, clientId, tabId).getEncoded(); | |
| assertEquals("gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.foo", encodedState); |
| package org.keycloak.broker.provider.util; | ||
|
|
||
| import org.junit.Test; | ||
| import org.keycloak.authorization.identity.Identity; |
There was a problem hiding this comment.
unused import, please remove the unused import.
|
In response to the SAML related stuff. I would like to refer to http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf (spedifically the section 3.4.3 RelayState) @mposolda: You seem to have a lot of SAML knowledge. Can you help? |
|
@evtr @ahus1 Thanks for the PR and review. But I am not a fan of the fix based on the static map It can work just when the cache of clientIdDigests is "preloaded" at startup, but this approach has other downsides (especially in environments with thousands of clients). I can see that possible approach is to replace For the time being, I suggest to workaround by using shorter |
|
@evtr / @mposolda - with the new store, clientModel.getId() will be a String as well, and usually a UUID (as before). A store might decide to use a different String. With dashes, it might even be 36 characters long. A properly compressed UUID could be as small as approx. 22 characters (but only if it is a UUID, and there is no guarantee for that at the moment). |
|
@ahus1 Ok, Thanks. In that case I suggest that:
|
|
@evtr / @mposolda - basically yes. The encoding of the UUID can be any encoding, please see a snippet to encode it via Base64 below that creates 22 bytes. Any encoding that is more efficient would create even shorter strings, but might clash on characters. The alphabet used by Base64 is not URL safe, I don't know if that is an issue with SAML: Here a code snippet to encode/decode. The padding with "=" is removed on encoding; Base64 won't need it on decode. |
We have |
|
It seem to me that the solution with using something more likely to be a uuid as id in this context would be an ok solution. This particular IdP has other constraints on the entityid (it must be a URL matching some of the other metadata), so in this case i cant just make up a shorter one, unfortunately, but I suppose that is ok for it to actually validate the relaystate according to the SAML specs. When I export my realm, I can se that the exported identityprovider has these properties: "identityProviders": [ I guess you are proposing using 01852927-7950-4c1c-89ca-d010e1f77c24 as input to the Base64.getEncoder().encodeToString(asBytes(uuid)).replace("=", "") I think it sound like a good solution. What to do next? |
|
@evtr - here's my suggestion where to start: The interface of IdentityBrokerState.encoded() and IdentityBrokerState.decoded() would need to change. For For Unfortunately this would make Let me know what you think. Can it be simplified? |
There is the issue that when you call |
|
@evtr yes, you can change the name of the parameter of One note is, that it is not 100% sure that In this case, it may be possible that relayState will be more than 80 characters long, but hopefully most of the deployments will be fine with this. |
Hi @mposolda - the code |
|
I was thinking to include the "resolve-method" in the clientid by prefix it with a "1" or a "0": My tests look like this: I thought that this could be a fairly easy change to do, but even simple changes make a kazillion tests fail: [ERROR] UsernameTemplateMapperTest.usernameShouldBeDerivedFromAliasAndIdpSubClaim:80->AbstractBaseBrokerTest.logInAsUserInIDP:226->AbstractBaseBrokerTest.logInWithBroker:245->AbstractBaseBrokerTest.logInWithIdp:254 » NoSuchElement |
|
@evtr Not exactly sure about the cause of this. But I suspect it can be due the reason that "clientDbId" is not neccessarily the UUID. You can try to enable some logging to see what is going on. Also make sure to handle the situation when the UUID is not used. Something like this (only pseudo-code for the illustration): This is just a suspicion and it is possible that test failures are caused by something different. Hopefully some debugging/logging can help you to figure it. |
|
My problem is actually more in the class SessionCodeChecks Here the clientid from the IdentityBrokerState is used to resolve back to the Client. I thought that it would be impractical to assume that it is the databaseid that is used, so I did this: So I think that it is necessary to somehow check if the clientid is the database id or the client id. Handling the uuid contra non uuid is easy enough: |
…entID to be URI Friendly
fix keycloak#15734
related keycloak#10227 keycloak#10231
Closes #10227 RelayState max length not respected