parent
f57560afd3
commit
4469bdc0a9
6 changed files with 1920 additions and 9 deletions
|
@ -17,6 +17,14 @@
|
|||
|
||||
package org.keycloak.broker.provider.util;
|
||||
|
||||
import org.keycloak.authorization.policy.evaluation.Realm;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
|
@ -30,20 +38,56 @@ public class IdentityBrokerState {
|
|||
private static final Pattern DOT = Pattern.compile("\\.");
|
||||
|
||||
|
||||
public static IdentityBrokerState decoded(String state, String clientId, String tabId) {
|
||||
String encodedState = state + "." + tabId + "." + clientId;
|
||||
public static IdentityBrokerState decoded(String state, String clientId, String clientClientId, String tabId) {
|
||||
|
||||
return new IdentityBrokerState(state, clientId, tabId, encodedState);
|
||||
String clientIdEncoded = clientClientId; // Default use the client.clientId
|
||||
if (clientId != null) {
|
||||
// According to (http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf) there is a limit on the relaystate of 80 bytes.
|
||||
// in order to try to adher to the SAML specification we use an encoded value of the client.id (probably UUID) instead of the with
|
||||
// probability bigger client.clientId. If the client.id is not in UUID format we just use the client.clientid as is
|
||||
try {
|
||||
UUID clientDbUuid = UUID.fromString(clientId);
|
||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||
bb.putLong(clientDbUuid.getMostSignificantBits());
|
||||
bb.putLong(clientDbUuid.getLeastSignificantBits());
|
||||
byte[] clientUuidBytes = bb.array();
|
||||
clientIdEncoded = Base64.getEncoder().encodeToString(clientUuidBytes).replace("=", "");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Ignore...the clientid in the database was not in UUID format. Just use as is.
|
||||
}
|
||||
}
|
||||
String encodedState = state + "." + tabId + "." + clientIdEncoded;
|
||||
|
||||
return new IdentityBrokerState(state, clientClientId, tabId, encodedState);
|
||||
}
|
||||
|
||||
|
||||
public static IdentityBrokerState encoded(String encodedState) {
|
||||
public static IdentityBrokerState encoded(String encodedState, RealmModel realmModel) {
|
||||
String[] decoded = DOT.split(encodedState, 3);
|
||||
|
||||
String state =(decoded.length > 0) ? decoded[0] : null;
|
||||
String tabId = (decoded.length > 1) ? decoded[1] : null;
|
||||
String clientId = (decoded.length > 2) ? decoded[2] : null;
|
||||
|
||||
if (clientId != null) {
|
||||
try {
|
||||
// If this decoding succeeds it was the result of the encoding of a UUID client.id - if it fails we interpret it as client.clientId
|
||||
// in accordance to the method decoded above
|
||||
byte[] decodedClientId = Base64.getDecoder().decode(clientId);
|
||||
ByteBuffer bb = ByteBuffer.wrap(decodedClientId);
|
||||
long first = bb.getLong();
|
||||
long second = bb.getLong();
|
||||
UUID clientDbUuid = new UUID(first, second);
|
||||
String clientIdInDb = clientDbUuid.toString();
|
||||
ClientModel clientModel = realmModel.getClientById(clientIdInDb);
|
||||
if (clientModel != null) {
|
||||
clientId = clientModel.getClientId();
|
||||
}
|
||||
} catch (IllegalArgumentException | BufferUnderflowException e) {
|
||||
// Ignore...the clientid was not in encoded uuid format. Just use as it is.
|
||||
}
|
||||
}
|
||||
|
||||
return new IdentityBrokerState(state, clientId, tabId, encodedState);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package org.keycloak.broker.provider.util;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.*;
|
||||
|
||||
|
||||
public class IdentityBrokerStateTest {
|
||||
|
||||
@Test
|
||||
public void testDecodedWithClientIdNotUuid() {
|
||||
|
||||
// Given
|
||||
String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk";
|
||||
String clientId = "something not a uuid";
|
||||
String clientClientId = "http://i.am.an.url";
|
||||
String tabId = "vpISZLVDAc0";
|
||||
|
||||
// When
|
||||
IdentityBrokerState encodedState = IdentityBrokerState.decoded(state, clientId, clientClientId, tabId);
|
||||
|
||||
// Then
|
||||
Assert.assertNotNull(encodedState);
|
||||
Assert.assertEquals(clientClientId, encodedState.getClientId());
|
||||
Assert.assertEquals(tabId, encodedState.getTabId());
|
||||
Assert.assertEquals("gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.http://i.am.an.url", encodedState.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodedWithClientIdAnActualUuid() {
|
||||
|
||||
// Given
|
||||
String state = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk";
|
||||
String clientId = "ed49448c-14cf-471e-a83a-063d0dc3bc8c";
|
||||
String clientClientId = "http://i.am.an.url";
|
||||
String tabId = "vpISZLVDAc0";
|
||||
|
||||
// When
|
||||
IdentityBrokerState encodedState = IdentityBrokerState.decoded(state, clientId, clientClientId, tabId);
|
||||
|
||||
// Then
|
||||
Assert.assertNotNull(encodedState);
|
||||
Assert.assertEquals(clientClientId, encodedState.getClientId());
|
||||
Assert.assertEquals(tabId, encodedState.getTabId());
|
||||
Assert.assertEquals("gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.7UlEjBTPRx6oOgY9DcO8jA", encodedState.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodedWithClientIdUUid() {
|
||||
// Given
|
||||
String encoded = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.7UlEjBTPRx6oOgY9DcO8jA";
|
||||
String clientId = "ed49448c-14cf-471e-a83a-063d0dc3bc8c";
|
||||
String clientClientId = "my-client-id";
|
||||
ClientModel clientModel = new IdentityBrokerStateTestHelpers.TestClientModel(clientId, clientClientId);
|
||||
RealmModel realmModel = new IdentityBrokerStateTestHelpers.TestRealmModel(clientId, clientClientId, clientModel);
|
||||
|
||||
// When
|
||||
IdentityBrokerState decodedState = IdentityBrokerState.encoded(encoded, realmModel);
|
||||
|
||||
// Then
|
||||
Assert.assertNotNull(decodedState);
|
||||
Assert.assertEquals(clientClientId, decodedState.getClientId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodedWithClientIdNotUUid() {
|
||||
// Given
|
||||
String encoded = "gNrGamIDGKpKSI9yOrcFzYTKoFGH779_WNCacAelkhk.vpISZLVDAc0.http://i.am.an.url";
|
||||
String clientId = "http://i.am.an.url";
|
||||
ClientModel clientModel = new IdentityBrokerStateTestHelpers.TestClientModel(clientId, clientId);
|
||||
RealmModel realmModel = new IdentityBrokerStateTestHelpers.TestRealmModel(clientId, clientId, clientModel);
|
||||
|
||||
// When
|
||||
IdentityBrokerState decodedState = IdentityBrokerState.encoded(encoded, realmModel);
|
||||
|
||||
// Then
|
||||
Assert.assertNotNull(decodedState);
|
||||
Assert.assertEquals("http://i.am.an.url", decodedState.getClientId());
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -550,8 +550,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
}
|
||||
|
||||
RealmModel realm = context.getRealm();
|
||||
|
||||
IdentityBrokerState idpBrokerState = IdentityBrokerState.encoded(stateParam);
|
||||
IdentityBrokerState idpBrokerState = IdentityBrokerState.encoded(stateParam, realm);
|
||||
ClientModel client = realm.getClientByClientId(idpBrokerState.getClientId());
|
||||
|
||||
AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(
|
||||
|
|
|
@ -1040,7 +1040,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
@Override
|
||||
public AuthenticationSessionModel getAndVerifyAuthenticationSession(String encodedCode) {
|
||||
IdentityBrokerState state = IdentityBrokerState.encoded(encodedCode);
|
||||
IdentityBrokerState state = IdentityBrokerState.encoded(encodedCode, realmModel);
|
||||
String code = state.getDecodedState();
|
||||
String clientId = state.getClientId();
|
||||
String tabId = state.getTabId();
|
||||
|
@ -1131,7 +1131,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
if (clientSessionCode != null) {
|
||||
authSession = clientSessionCode.getClientSession();
|
||||
String relayState = clientSessionCode.getOrGenerateCode();
|
||||
encodedState = IdentityBrokerState.decoded(relayState, authSession.getClient().getClientId(), authSession.getTabId());
|
||||
encodedState = IdentityBrokerState.decoded(relayState, authSession.getClient().getId(), authSession.getClient().getClientId(), authSession.getTabId());
|
||||
}
|
||||
|
||||
return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.session.getContext().getUri(), encodedState, getRedirectUri(providerId));
|
||||
|
|
|
@ -185,7 +185,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
public Response authResponse(@QueryParam("state") String state,
|
||||
@QueryParam("denied") String denied,
|
||||
@QueryParam("oauth_verifier") String verifier) {
|
||||
IdentityBrokerState idpState = IdentityBrokerState.encoded(state);
|
||||
IdentityBrokerState idpState = IdentityBrokerState.encoded(state, realm);
|
||||
String clientId = idpState.getClientId();
|
||||
String tabId = idpState.getTabId();
|
||||
if (clientId == null || tabId == null) {
|
||||
|
|
Loading…
Reference in a new issue