Add pairwise sub support to authorization services

Identity token verification will now fetch the user from the session
state instead of relying on the sub provided in the token. Also done in
KeycloakIdentity.

Resolves: KEYCLOAK-6659
This commit is contained in:
Martin Hardselius 2018-03-02 13:08:27 +01:00
parent 9b1275f182
commit 8549bd70b7
7 changed files with 229 additions and 52 deletions

View file

@ -212,7 +212,7 @@ public class KeycloakIdentity implements Identity {
return client==null ? null : client.getId();
}
return this.accessToken.getSubject();
return this.getUserFromSessionState().getId();
}
@Override
@ -237,7 +237,7 @@ public class KeycloakIdentity implements Identity {
return false;
}
return this.accessToken.getSubject().equals(clientUser.getId());
return this.getUserFromSessionState().getId().equals(clientUser.getId());
}
private ClientModel getTargetClient() {
@ -252,4 +252,9 @@ public class KeycloakIdentity implements Identity {
return null;
}
private UserModel getUserFromSessionState() {
UserSessionModel userSession = keycloakSession.sessions().getUserSession(realm, accessToken.getSessionState());
return userSession.getUser();
}
}

View file

@ -1082,24 +1082,28 @@ public class AuthenticationManager {
}
}
UserModel user = session.users().getUserById(token.getSubject(), realm);
if (user == null || !user.isEnabled() ) {
logger.debug("Unknown user in identity token");
return null;
}
int userNotBefore = session.users().getNotBeforeOfUser(realm, user);
if (token.getIssuedAt() < userNotBefore) {
logger.debug("User notBefore newer than token");
return null;
}
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
UserModel user = null;
if (userSession != null) {
user = userSession.getUser();
if (user == null || !user.isEnabled()) {
logger.debug("Unknown user in identity token");
return null;
}
int userNotBefore = session.users().getNotBeforeOfUser(realm, user);
if (token.getIssuedAt() < userNotBefore) {
logger.debug("User notBefore newer than token");
return null;
}
}
if (!isSessionValid(realm, userSession)) {
// Check if accessToken was for the offline session.
if (!isCookie) {
UserSessionModel offlineUserSession = session.sessions().getOfflineUserSession(realm, token.getSessionState());
if (isOfflineSessionValid(realm, offlineUserSession)) {
user = offlineUserSession.getUser();
return new AuthResult(user, offlineUserSession, token);
}
}

View file

@ -53,29 +53,53 @@ import org.keycloak.util.JsonSerialization;
*/
public class AuthorizationAPITest extends AbstractAuthzTest {
private static final String RESOURCE_SERVER_TEST = "resource-server-test";
private static final String TEST_CLIENT = "test-client";
private static final String AUTHZ_CLIENT_CONFIG = "default-keycloak.json";
private static final String PAIRWISE_RESOURCE_SERVER_TEST = "pairwise-resource-server-test";
private static final String PAIRWISE_TEST_CLIENT = "test-client-pairwise";
private static final String PAIRWISE_AUTHZ_CLIENT_CONFIG = "default-keycloak-pairwise.json";
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(RealmBuilder.create().name("authz-test")
.roles(RolesBuilder.create().realmRole(RoleBuilder.create().name("uma_authorization").build()))
.user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization"))
.user(UserBuilder.create().username("kolo").password("password"))
.client(ClientBuilder.create().clientId("resource-server-test")
.client(ClientBuilder.create().clientId(RESOURCE_SERVER_TEST)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants())
.client(ClientBuilder.create().clientId("test-client")
.client(ClientBuilder.create().clientId(PAIRWISE_RESOURCE_SERVER_TEST)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants()
.pairwise("http://pairwise.com"))
.client(ClientBuilder.create().clientId(TEST_CLIENT)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/test-client")
.directAccessGrants())
.client(ClientBuilder.create().clientId(PAIRWISE_TEST_CLIENT)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/test-client")
.directAccessGrants())
.build());
}
@Before
public void configureAuthorization() throws Exception {
ClientResource client = getClient(getRealm());
configureAuthorization(RESOURCE_SERVER_TEST);
configureAuthorization(PAIRWISE_RESOURCE_SERVER_TEST);
}
private void configureAuthorization(String clientId) throws Exception {
ClientResource client = getClient(getRealm(), clientId);
AuthorizationResource authorization = client.authorization();
ResourceRepresentation resource = new ResourceRepresentation("Resource A");
@ -102,7 +126,16 @@ public class AuthorizationAPITest extends AbstractAuthzTest {
@Test
public void testAccessTokenWithUmaAuthorization() {
AuthzClient authzClient = getAuthzClient();
testAccessTokenWithUmaAuthorization(AUTHZ_CLIENT_CONFIG);
}
@Test
public void testAccessTokenWithUmaAuthorizationPairwise() {
testAccessTokenWithUmaAuthorization(PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
public void testAccessTokenWithUmaAuthorization(String authzConfigFile) {
AuthzClient authzClient = getAuthzClient(authzConfigFile);
PermissionRequest request = new PermissionRequest("Resource A");
String ticket = authzClient.protection().permission().create(request).getTicket();
@ -113,32 +146,63 @@ public class AuthorizationAPITest extends AbstractAuthzTest {
@Test
public void testResourceServerAsAudience() throws Exception {
AuthzClient authzClient = getAuthzClient();
testResourceServerAsAudience(
TEST_CLIENT,
RESOURCE_SERVER_TEST,
AUTHZ_CLIENT_CONFIG);
}
@Test
public void testResourceServerAsAudienceWithPairwiseClient() throws Exception {
testResourceServerAsAudience(
PAIRWISE_TEST_CLIENT,
RESOURCE_SERVER_TEST,
AUTHZ_CLIENT_CONFIG);
}
@Test
public void testPairwiseResourceServerAsAudience() throws Exception {
testResourceServerAsAudience(
TEST_CLIENT,
PAIRWISE_RESOURCE_SERVER_TEST,
PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
@Test
public void testPairwiseResourceServerAsAudienceWithPairwiseClient() throws Exception {
testResourceServerAsAudience(
PAIRWISE_TEST_CLIENT,
PAIRWISE_RESOURCE_SERVER_TEST,
PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
public void testResourceServerAsAudience(String clientId, String resourceServerClientId, String authzConfigFile) throws Exception {
AuthzClient authzClient = getAuthzClient(authzConfigFile);
PermissionRequest request = new PermissionRequest();
request.setResourceId("Resource A");
String accessToken = new OAuthClient().realm("authz-test").clientId("test-client").doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
String accessToken = new OAuthClient().realm("authz-test").clientId(clientId).doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
String ticket = authzClient.protection().permission().create(request).getTicket();
AuthorizationResponse response = authzClient.authorization(accessToken).authorize(new AuthorizationRequest(ticket));
assertNotNull(response.getToken());
AccessToken rpt = toAccessToken(response.getToken());
assertEquals("resource-server-test", rpt.getAudience()[0]);
assertEquals(resourceServerClientId, rpt.getAudience()[0]);
}
private RealmResource getRealm() throws Exception {
return adminClient.realm("authz-test");
}
private ClientResource getClient(RealmResource realm) {
private ClientResource getClient(RealmResource realm, String clientId) {
ClientsResource clients = realm.clients();
return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
return clients.findByClientId(clientId).stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
}
private AuthzClient getAuthzClient() {
private AuthzClient getAuthzClient(String configFile) {
try {
return AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
return AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/" + configFile), Configuration.class));
} catch (IOException cause) {
throw new RuntimeException("Failed to create authz client", cause);
}

View file

@ -55,6 +55,13 @@ import org.keycloak.util.JsonSerialization;
*/
public class EntitlementAPITest extends AbstractAuthzTest {
private static final String RESOURCE_SERVER_TEST = "resource-server-test";
private static final String TEST_CLIENT = "test-client";
private static final String AUTHZ_CLIENT_CONFIG = "default-keycloak.json";
private static final String PAIRWISE_RESOURCE_SERVER_TEST = "pairwise-resource-server-test";
private static final String PAIRWISE_TEST_CLIENT = "test-client-pairwise";
private static final String PAIRWISE_AUTHZ_CLIENT_CONFIG = "default-keycloak-pairwise.json";
private AuthzClient authzClient;
@Override
@ -63,23 +70,41 @@ public class EntitlementAPITest extends AbstractAuthzTest {
.roles(RolesBuilder.create().realmRole(RoleBuilder.create().name("uma_authorization").build()))
.user(UserBuilder.create().username("marta").password("password").addRoles("uma_authorization"))
.user(UserBuilder.create().username("kolo").password("password"))
.client(ClientBuilder.create().clientId("resource-server-test")
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants())
.client(ClientBuilder.create().clientId("test-client")
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/test-client")
.directAccessGrants())
.client(ClientBuilder.create().clientId(RESOURCE_SERVER_TEST)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.directAccessGrants())
.client(ClientBuilder.create().clientId(PAIRWISE_RESOURCE_SERVER_TEST)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/resource-server-test")
.defaultRoles("uma_protection")
.pairwise("http://pairwise.com")
.directAccessGrants())
.client(ClientBuilder.create().clientId(TEST_CLIENT)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/test-client")
.directAccessGrants())
.client(ClientBuilder.create().clientId(PAIRWISE_TEST_CLIENT)
.secret("secret")
.authorizationServicesEnabled(true)
.redirectUris("http://localhost/test-client")
.pairwise("http://pairwise.com")
.directAccessGrants())
.build());
}
@Before
public void configureAuthorization() throws Exception {
ClientResource client = getClient(getRealm());
configureAuthorization(RESOURCE_SERVER_TEST);
configureAuthorization(PAIRWISE_RESOURCE_SERVER_TEST);
}
public void configureAuthorization(String clientId) throws Exception {
ClientResource client = getClient(getRealm(), clientId);
AuthorizationResource authorization = client.authorization();
JSPolicyRepresentation policy = new JSPolicyRepresentation();
@ -106,6 +131,15 @@ public class EntitlementAPITest extends AbstractAuthzTest {
@Test
public void testRptRequestWithoutResourceName() {
testRptRequestWithoutResourceName(AUTHZ_CLIENT_CONFIG);
}
@Test
public void testRptRequestWithoutResourceNamePairwise() {
testRptRequestWithoutResourceName(PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
public void testRptRequestWithoutResourceName(String configFile) {
Metadata metadata = new Metadata();
metadata.setIncludeResourceName(false);
@ -116,32 +150,51 @@ public class EntitlementAPITest extends AbstractAuthzTest {
request.setMetadata(metadata);
request.addPermission("Resource 1");
return getAuthzClient().authorization("marta", "password").authorize(request);
return getAuthzClient(configFile).authorization("marta", "password").authorize(request);
});
}
@Test
public void testRptRequestWithResourceName() {
testRptRequestWithResourceName(AUTHZ_CLIENT_CONFIG);
}
@Test
public void testRptRequestWithResourceNamePairwise() {
testRptRequestWithResourceName(PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
public void testRptRequestWithResourceName(String configFile) {
Metadata metadata = new Metadata();
metadata.setIncludeResourceName(true);
assertResponse(metadata, () -> getAuthzClient().authorization("marta", "password").authorize());
assertResponse(metadata, () -> getAuthzClient(configFile).authorization("marta", "password").authorize());
AuthorizationRequest request = new AuthorizationRequest();
request.setMetadata(metadata);
request.addPermission("Resource 13");
assertResponse(metadata, () -> getAuthzClient().authorization("marta", "password").authorize(request));
assertResponse(metadata, () -> getAuthzClient(configFile).authorization("marta", "password").authorize(request));
request.setMetadata(null);
assertResponse(metadata, () -> getAuthzClient().authorization("marta", "password").authorize(request));
assertResponse(metadata, () -> getAuthzClient(configFile).authorization("marta", "password").authorize(request));
}
@Test
public void testPermissionLimit() {
testPermissionLimit(AUTHZ_CLIENT_CONFIG);
}
@Test
public void testPermissionLimitPairwise() {
testPermissionLimit(PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
public void testPermissionLimit(String configFile) {
AuthorizationRequest request = new AuthorizationRequest();
for (int i = 1; i <= 10; i++) {
@ -154,7 +207,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
request.setMetadata(metadata);
AuthorizationResponse response = getAuthzClient().authorization("marta", "password").authorize(request);
AuthorizationResponse response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
AccessToken rpt = toAccessToken(response.getToken());
List<Permission> permissions = rpt.getAuthorization().getPermissions();
@ -174,7 +227,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
request.setMetadata(metadata);
request.setRpt(response.getToken());
response = getAuthzClient().authorization("marta", "password").authorize(request);
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
rpt = toAccessToken(response.getToken());
permissions = rpt.getAuthorization().getPermissions();
@ -198,7 +251,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
request.setMetadata(metadata);
request.setRpt(response.getToken());
response = getAuthzClient().authorization("marta", "password").authorize(request);
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
rpt = toAccessToken(response.getToken());
permissions = rpt.getAuthorization().getPermissions();
@ -221,7 +274,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
request.setMetadata(metadata);
request.setRpt(response.getToken());
response = getAuthzClient().authorization("marta", "password").authorize(request);
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
rpt = toAccessToken(response.getToken());
permissions = rpt.getAuthorization().getPermissions();
@ -236,15 +289,46 @@ public class EntitlementAPITest extends AbstractAuthzTest {
@Test
public void testResourceServerAsAudience() throws Exception {
testResourceServerAsAudience(
TEST_CLIENT,
RESOURCE_SERVER_TEST,
AUTHZ_CLIENT_CONFIG);
}
@Test
public void testResourceServerAsAudienceWithPairwiseClient() throws Exception {
testResourceServerAsAudience(
PAIRWISE_TEST_CLIENT,
RESOURCE_SERVER_TEST,
AUTHZ_CLIENT_CONFIG);
}
@Test
public void testPairwiseResourceServerAsAudience() throws Exception {
testResourceServerAsAudience(
TEST_CLIENT,
PAIRWISE_RESOURCE_SERVER_TEST,
PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
@Test
public void testPairwiseResourceServerAsAudienceWithPairwiseClient() throws Exception {
testResourceServerAsAudience(
PAIRWISE_TEST_CLIENT,
PAIRWISE_RESOURCE_SERVER_TEST,
PAIRWISE_AUTHZ_CLIENT_CONFIG);
}
public void testResourceServerAsAudience(String testClientId, String resourceServerClientId, String configFile) throws Exception {
AuthorizationRequest request = new AuthorizationRequest();
request.addPermission("Resource 1");
String accessToken = new OAuthClient().realm("authz-test").clientId("test-client").doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
AuthorizationResponse response = getAuthzClient().authorization(accessToken).authorize(request);
String accessToken = new OAuthClient().realm("authz-test").clientId(testClientId).doGrantAccessTokenRequest("secret", "marta", "password").getAccessToken();
AuthorizationResponse response = getAuthzClient(configFile).authorization(accessToken).authorize(request);
AccessToken rpt = toAccessToken(response.getToken());
assertEquals("resource-server-test", rpt.getAudience()[0]);
assertEquals(resourceServerClientId, rpt.getAudience()[0]);
}
private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
@ -268,15 +352,15 @@ public class EntitlementAPITest extends AbstractAuthzTest {
return adminClient.realm("authz-test");
}
private ClientResource getClient(RealmResource realm) {
private ClientResource getClient(RealmResource realm, String clientId) {
ClientsResource clients = realm.clients();
return clients.findByClientId("resource-server-test").stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
return clients.findByClientId(clientId).stream().map(representation -> clients.get(representation.getId())).findFirst().orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]"));
}
private AuthzClient getAuthzClient() {
private AuthzClient getAuthzClient(String configFile) {
if (authzClient == null) {
try {
authzClient = AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/default-keycloak.json"), Configuration.class));
authzClient = AuthzClient.create(JsonSerialization.readValue(getClass().getResourceAsStream("/authorization-test/" + configFile), Configuration.class));
} catch (IOException cause) {
throw new RuntimeException("Failed to create authz client", cause);
}

View file

@ -198,4 +198,12 @@ public class ClientBuilder {
rep.getProtocolMappers().addAll(Arrays.asList(mappers));
return this;
}
public ClientBuilder pairwise(String sectorIdentifierUri, String salt) {
return protocolMapper(ProtocolMapperUtil.createPairwiseMapper(sectorIdentifierUri, salt));
}
public ClientBuilder pairwise(String sectorIdentifierUri) {
return protocolMapper(ProtocolMapperUtil.createPairwiseMapper(sectorIdentifierUri, null));
}
}

View file

@ -6,6 +6,7 @@ import org.keycloak.protocol.oidc.mappers.AddressMapper;
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
import org.keycloak.protocol.oidc.mappers.HardcodedRole;
import org.keycloak.protocol.oidc.mappers.RoleNameMapper;
import org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper;
import org.keycloak.protocol.oidc.mappers.ScriptBasedOIDCProtocolMapper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
@ -164,4 +165,7 @@ public class ProtocolMapperUtil {
);
}
public static ProtocolMapperRepresentation createPairwiseMapper(String sectorIdentifierUri, String salt) {
return SHA256PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri, salt);
}
}

View file

@ -0,0 +1,8 @@
{
"realm": "authz-test",
"auth-server-url" : "http://localhost:8180/auth",
"resource" : "pairwise-resource-server-test",
"credentials": {
"secret": "secret"
}
}