Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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;

return new IdentityBrokerState(state, clientId, tabId, encodedState);
public static IdentityBrokerState decoded(String state, String clientId, String clientClientId, String tabId) {

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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}

}
Loading