More options to organization scope mapper including adding organization attributes to tokens
Closes #31642 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
bacfeed7b1
commit
449557290b
4 changed files with 267 additions and 53 deletions
|
@ -3240,3 +3240,5 @@ sentInvitation=Sent invitation
|
|||
loggedInAsTempAdminUser=You are logged in as a temporary admin user. To harden security, create a permanent admin account and delete the temporary one.
|
||||
temporaryAdmin=Temporary admin user account. Ensure it is replaced with a permanent admin user account as soon as possible.
|
||||
temporaryService=Temporary admin service account. Ensure it is replaced with a permanent admin service account as soon as possible.
|
||||
addOrganizationAttributes.label=Add organization attributes
|
||||
addOrganizationAttributes.help=If enabled, the organization attributes will be available for each organization mapped to the token.
|
|
@ -17,22 +17,30 @@
|
|||
|
||||
package org.keycloak.organization.protocol.mappers.oidc;
|
||||
|
||||
import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.JSON_TYPE;
|
||||
import static org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
||||
|
@ -47,11 +55,28 @@ import org.keycloak.representations.IDToken;
|
|||
public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper, TokenIntrospectionTokenMapper, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "oidc-organization-membership-mapper";
|
||||
public static final String ADD_ORGANIZATION_ATTRIBUTES = "addOrganizationAttributes";
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
List<ProviderConfigProperty> properties = new ArrayList<>();
|
||||
OIDCAttributeMapperHelper.addTokenClaimNameConfig(properties);
|
||||
OIDCAttributeMapperHelper.addIncludeInTokensConfig(properties, OrganizationMembershipMapper.class);
|
||||
OIDCAttributeMapperHelper.addJsonTypeConfig(properties, List.of("String", "JSON"), "String");
|
||||
ProviderConfigProperty property = new ProviderConfigProperty();
|
||||
property.setName(ProtocolMapperUtils.MULTIVALUED);
|
||||
property.setLabel(ProtocolMapperUtils.MULTIVALUED_LABEL);
|
||||
property.setHelpText(ProtocolMapperUtils.MULTIVALUED_HELP_TEXT);
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
property.setDefaultValue(Boolean.TRUE.toString());
|
||||
properties.add(property);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ADD_ORGANIZATION_ATTRIBUTES);
|
||||
property.setLabel(ADD_ORGANIZATION_ATTRIBUTES + ".label");
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
property.setDefaultValue(Boolean.FALSE.toString());
|
||||
property.setHelpText(ADD_ORGANIZATION_ATTRIBUTES + ".help");
|
||||
properties.add(property);
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -76,7 +101,7 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) {
|
||||
protected void setClaim(IDToken token, ProtocolMapperModel model, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) {
|
||||
String rawScopes = clientSessionCtx.getScopeString();
|
||||
OrganizationScope scope = OrganizationScope.valueOfScope(rawScopes);
|
||||
|
||||
|
@ -93,16 +118,94 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
organizations = Stream.of(session.getProvider(OrganizationProvider.class).getById(orgId));
|
||||
}
|
||||
|
||||
KeycloakContext context = session.getContext();
|
||||
RealmModel realm = context.getRealm();
|
||||
ProtocolMapperModel effectiveModel = getEffectiveModel(session, realm, model);
|
||||
|
||||
Map<String, Map<String, Object>> claim = new HashMap<>();
|
||||
Object claim = resolveValue(effectiveModel, organizations.toList());
|
||||
|
||||
organizations.filter(Objects::nonNull).forEach(o -> claim.put(o.getAlias(), Map.of()));
|
||||
|
||||
if (claim.isEmpty()) {
|
||||
if (claim == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
token.getOtherClaims().put(OAuth2Constants.ORGANIZATION, claim);
|
||||
OIDCAttributeMapperHelper.mapClaim(token, effectiveModel, claim);
|
||||
}
|
||||
|
||||
private Object resolveValue(ProtocolMapperModel model, List<OrganizationModel> organizations) {
|
||||
if (organizations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!OIDCAttributeMapperHelper.isMultivalued(model)) {
|
||||
return organizations.get(0).getName();
|
||||
}
|
||||
|
||||
Map<String, Map<String, Object>> value = new HashMap<>();
|
||||
|
||||
for (OrganizationModel o : organizations) {
|
||||
if (o == null || !o.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> attributes = Map.of();
|
||||
|
||||
if (isAddOrganizationAttributes(model)) {
|
||||
attributes = new HashMap<>(o.getAttributes());
|
||||
}
|
||||
|
||||
value.put(o.getAlias(), attributes);
|
||||
}
|
||||
|
||||
if (value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isJsonType(model)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.keySet();
|
||||
}
|
||||
|
||||
private static boolean isJsonType(ProtocolMapperModel model) {
|
||||
return "JSON".equals(model.getConfig().getOrDefault(JSON_TYPE, "JSON"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getEffectiveModel(KeycloakSession session, RealmModel realm, ProtocolMapperModel model) {
|
||||
// Effectively clone
|
||||
ProtocolMapperModel copy = RepresentationToModel.toModel(ModelToRepresentation.toRepresentation(model));
|
||||
Map<String, String> config = Optional.ofNullable(copy.getConfig()).orElseGet(HashMap::new);
|
||||
|
||||
config.putIfAbsent(JSON_TYPE, "String");
|
||||
|
||||
if (!OIDCAttributeMapperHelper.isMultivalued(copy)) {
|
||||
config.put(ADD_ORGANIZATION_ATTRIBUTES, Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
if (isAddOrganizationAttributes(copy)) {
|
||||
config.put(JSON_TYPE, "JSON");
|
||||
}
|
||||
|
||||
setDefaultValues(config);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void setDefaultValues(Map<String, String> config) {
|
||||
config.putIfAbsent(TOKEN_CLAIM_NAME, OAuth2Constants.ORGANIZATION);
|
||||
|
||||
for (ProviderConfigProperty property : getConfigProperties()) {
|
||||
Object defaultValue = property.getDefaultValue();
|
||||
|
||||
if (defaultValue != null) {
|
||||
config.putIfAbsent(ProtocolMapperUtils.MULTIVALUED, defaultValue.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAddOrganizationAttributes(ProtocolMapperModel model) {
|
||||
return Boolean.parseBoolean(model.getConfig().getOrDefault(ADD_ORGANIZATION_ATTRIBUTES, Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean introspectionEndpoint) {
|
||||
|
@ -114,6 +217,9 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||
if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
||||
if (introspectionEndpoint) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_INTROSPECTION, "true");
|
||||
config.put(TOKEN_CLAIM_NAME, OAuth2Constants.ORGANIZATION);
|
||||
config.put(JSON_TYPE, "String");
|
||||
config.put(ProtocolMapperUtils.MULTIVALUED, Boolean.TRUE.toString());
|
||||
mapper.setConfig(config);
|
||||
|
||||
return mapper;
|
||||
|
|
|
@ -442,18 +442,17 @@ public class OIDCAttributeMapperHelper {
|
|||
}
|
||||
|
||||
public static void addJsonTypeConfig(List<ProviderConfigProperty> configProperties) {
|
||||
addJsonTypeConfig(configProperties, List.of("String", "long", "int", "boolean", "JSON"), null);
|
||||
}
|
||||
|
||||
public static void addJsonTypeConfig(List<ProviderConfigProperty> configProperties, List<String> supportedTypes, String defaultValue) {
|
||||
ProviderConfigProperty property = new ProviderConfigProperty();
|
||||
property.setName(JSON_TYPE);
|
||||
property.setLabel(JSON_TYPE);
|
||||
List<String> types = new ArrayList<>(5);
|
||||
types.add("String");
|
||||
types.add("long");
|
||||
types.add("int");
|
||||
types.add("boolean");
|
||||
types.add("JSON");
|
||||
property.setType(ProviderConfigProperty.LIST_TYPE);
|
||||
property.setOptions(types);
|
||||
property.setOptions(supportedTypes);
|
||||
property.setHelpText(JSON_TYPE_TOOLTIP);
|
||||
property.setDefaultValue(defaultValue);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
package org.keycloak.testsuite.organization.mapper;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.oneOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -31,24 +33,31 @@ import static org.junit.Assert.assertTrue;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientScopeResource;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.organization.protocol.mappers.oidc.OrganizationMembershipMapper;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.MemberRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
|
@ -62,6 +71,12 @@ import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
|
|||
@EnableFeature(Feature.ORGANIZATION)
|
||||
public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest {
|
||||
|
||||
@Before
|
||||
public void onBefore() {
|
||||
setMapperConfig(OIDCAttributeMapperHelper.JSON_TYPE, null);
|
||||
setMapperConfig(ProtocolMapperUtils.MULTIVALUED, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordGrantType() throws Exception {
|
||||
OrganizationResource orga = testRealm().organizations().get(createOrganization("org-a").getId());
|
||||
|
@ -85,13 +100,12 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
|
||||
Map<String, Object> claim = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
List<String> claim = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(claim, notNullValue());
|
||||
assertThat(claim.get(orga.toRepresentation().getName()), notNullValue());
|
||||
String orgaId = orga.toRepresentation().getName();
|
||||
String orgbId = orgb.toRepresentation().getName();
|
||||
assertThat(claim.get(orgaId), notNullValue());
|
||||
assertThat(claim.get(orgbId), notNullValue());
|
||||
String orgaName = orga.toRepresentation().getName();
|
||||
String orgbName = orgb.toRepresentation().getName();
|
||||
assertThat(claim.contains(orgaName), is(true));
|
||||
assertThat(claim.contains(orgbName), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -171,10 +185,10 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(2));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgB.getAlias()), is(true));
|
||||
assertThat(response.getRefreshToken(), notNullValue());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
assertThat(refreshToken.getScope(), containsString(orgScope));
|
||||
|
@ -183,10 +197,10 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(2));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgB.getAlias()), is(true));
|
||||
refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
assertThat(refreshToken.getScope(), containsString(orgScope));
|
||||
}
|
||||
|
@ -229,10 +243,10 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
// once we support the user to select an organization, the selected organization will be mapped
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(false));
|
||||
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(false));
|
||||
assertThat(organizations.contains(orgB.getAlias()), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -254,7 +268,7 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(2));
|
||||
orgScope = "organization:orga";
|
||||
oauth.scope(orgScope);
|
||||
|
@ -263,9 +277,9 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(1));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -288,9 +302,9 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(1));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(true));
|
||||
orgScope = "organization:*";
|
||||
oauth.scope(orgScope);
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||
|
@ -298,9 +312,9 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(originalScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(1));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -323,9 +337,9 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(1));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(orgA.getAlias()), is(true));
|
||||
orgScope = "organization:orgb";
|
||||
oauth.scope(orgScope);
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||
|
@ -358,9 +372,9 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
// once we support the user to select an organization, the selected organization will be mapped
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.contains(orgB.getAlias()), is(true));
|
||||
String orgScope = "organization:*";
|
||||
oauth.scope(orgScope);
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||
|
@ -368,8 +382,8 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(originalScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||
organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.contains(orgB.getAlias()), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -390,13 +404,11 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
loginPage.login(memberPassword);
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||
// for now, return the organization scope in the response and access token even though no organization is mapped into the token
|
||||
// once we support the user to select an organization, the selected organization will be mapped
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.contains(orgB.getAlias()), is(true));
|
||||
String orgScope = "organization:orgb";
|
||||
oauth.scope(orgScope);
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||
|
@ -406,6 +418,80 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
assertThat(accessToken.getOtherClaims().keySet(), not(hasItem(OAuth2Constants.ORGANIZATION)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncludeOrganizationAttributes() throws Exception {
|
||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||
addMember(organization);
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ATTRIBUTES, Boolean.TRUE.toString());
|
||||
|
||||
oauth.clientId("direct-grant");
|
||||
oauth.scope("openid organization");
|
||||
AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", memberEmail, memberPassword);
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
Map<String, Map<String, List<String>>> organizations = (Map<String, Map<String, List<String>>>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.keySet(), hasItem(organizationName));
|
||||
assertThat(organizations.get(organizationName).keySet(), hasItem("key"));
|
||||
assertThat(organizations.get(organizationName).get("key"), containsInAnyOrder("value1", "value2"));
|
||||
|
||||
// when attributes are added to tokens, the claim type is a json regardless of the value set in the config
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ATTRIBUTES, Boolean.TRUE.toString());
|
||||
setMapperConfig(OIDCAttributeMapperHelper.JSON_TYPE, "boolean");
|
||||
response = oauth.doGrantAccessTokenRequest("password", memberEmail, memberPassword);
|
||||
accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Map<String, List<String>>>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.keySet(), hasItem(organizationName));
|
||||
assertThat(organizations.get(organizationName).keySet(), hasItem("key"));
|
||||
assertThat(organizations.get(organizationName).get("key"), containsInAnyOrder("value1", "value2"));
|
||||
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ATTRIBUTES, Boolean.FALSE.toString());
|
||||
setMapperConfig(OIDCAttributeMapperHelper.JSON_TYPE, "JSON");
|
||||
response = oauth.doGrantAccessTokenRequest("password", memberEmail, memberPassword);
|
||||
accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Map<String, List<String>>>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.keySet(), hasItem(organizationName));
|
||||
assertThat(organizations.get(organizationName).keySet().isEmpty(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrganizationsClaimAsList() throws Exception {
|
||||
OrganizationRepresentation orgA = createOrganization("orga", Map.of(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString()));
|
||||
MemberRepresentation member = addMember(testRealm().organizations().get(orgA.getId()), "member@" + orgA.getDomains().iterator().next().getName());
|
||||
OrganizationRepresentation orgB = createOrganization("orgb", Map.of(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString()));
|
||||
testRealm().organizations().get(orgB.getId()).members().addMember(member.getId()).close();
|
||||
|
||||
setMapperConfig(OIDCAttributeMapperHelper.JSON_TYPE, "String");
|
||||
oauth.clientId("direct-grant");
|
||||
oauth.scope("openid organization:*");
|
||||
AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", member.getEmail(), memberPassword);
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations, containsInAnyOrder("orga", "orgb"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrganizationsClaimSingleValued() throws Exception {
|
||||
OrganizationRepresentation orgA = createOrganization("orga", Map.of(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString()));
|
||||
MemberRepresentation member = addMember(testRealm().organizations().get(orgA.getId()), "member@" + orgA.getDomains().iterator().next().getName());
|
||||
OrganizationRepresentation orgB = createOrganization("orgb", Map.of(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString()));
|
||||
testRealm().organizations().get(orgB.getId()).members().addMember(member.getId()).close();
|
||||
|
||||
setMapperConfig(ProtocolMapperUtils.MULTIVALUED, Boolean.FALSE.toString());
|
||||
oauth.clientId("direct-grant");
|
||||
oauth.scope("openid organization:*");
|
||||
AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", member.getEmail(), memberPassword);
|
||||
assertThat(response.getScope(), containsString("organization"));
|
||||
AccessToken accessToken = TokenVerifier.create(response.getAccessToken(), AccessToken.class).getToken();
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
String organization = (String) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organization, is(oneOf("orga", "orgb")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidOrganizationScope() throws MalformedURLException {
|
||||
oauth.clientId("broker-app");
|
||||
|
@ -429,16 +515,15 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
return groupMapper;
|
||||
}
|
||||
|
||||
private void assertScopeAndClaims(String orgScope, OrganizationRepresentation orgA) {
|
||||
private void assertScopeAndClaims(String orgScope, OrganizationRepresentation org) {
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||
assertThat(response.getScope(), containsString(orgScope));
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
Map<String, Object> organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(1));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
List<String> organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.contains(org.getAlias()), is(true));
|
||||
assertThat(response.getRefreshToken(), notNullValue());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
assertThat(refreshToken.getScope(), containsString(orgScope));
|
||||
|
@ -447,10 +532,32 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
assertThat(accessToken.getScope(), containsString(orgScope));
|
||||
assertThat(accessToken.getOtherClaims().keySet(), hasItem(OAuth2Constants.ORGANIZATION));
|
||||
organizations = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
organizations = (List<String>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.size(), is(1));
|
||||
assertThat(organizations.containsKey(orgA.getAlias()), is(true));
|
||||
assertThat(organizations.contains(org.getAlias()), is(true));
|
||||
refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
assertThat(refreshToken.getScope(), containsString(orgScope));
|
||||
}
|
||||
|
||||
private void setMapperConfig(String key, String value) {
|
||||
ClientScopeRepresentation orgScope = testRealm().clientScopes().findAll().stream()
|
||||
.filter(s -> OIDCLoginProtocolFactory.ORGANIZATION.equals(s.getName()))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
ClientScopeResource orgScopeResource = testRealm().clientScopes().get(orgScope.getId());
|
||||
ProtocolMapperRepresentation orgMapper = orgScopeResource.getProtocolMappers().getMappers().stream()
|
||||
.filter(m -> OIDCLoginProtocolFactory.ORGANIZATION.equals(m.getName()))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
|
||||
Map<String, String> config = orgMapper.getConfig();
|
||||
|
||||
if (value == null) {
|
||||
config.remove(key);
|
||||
} else {
|
||||
config.put(key, value);
|
||||
}
|
||||
|
||||
orgScopeResource.getProtocolMappers().update(orgMapper.getId(), orgMapper);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue