Add option to include the organization id in the organization claims
Closes #32746 Signed-off-by: Maksim Zvankovich <m.zvankovich@nexovagroup.eu> Co-authored-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
parent
aacdf80664
commit
35eba8be8c
6 changed files with 68 additions and 6 deletions
|
@ -93,6 +93,7 @@ public interface OAuth2Constants {
|
|||
String SCOPE_PHONE = "phone";
|
||||
|
||||
String ORGANIZATION = "organization";
|
||||
String ORGANIZATION_ID = "id";
|
||||
|
||||
String UI_LOCALES_PARAM = "ui_locales";
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 80 KiB |
|
@ -11,6 +11,7 @@ As a result, the token will contain a claim as follows:
|
|||
```json
|
||||
"organization": {
|
||||
"testcorp": {
|
||||
"id": "42c3e46f-2477-44d7-a85b-d3b43f6b31fa",
|
||||
"attr1": [
|
||||
"value1"
|
||||
]
|
||||
|
@ -23,7 +24,7 @@ to authorize access to protected resources based on the organization where the u
|
|||
|
||||
The `organization` scope is a built-in optional client scope at the realm. Therefore, this scope is added to any client created in the realm by default. It also defines the `Organization Membership` mapper that controls how the organization membership information is mapped to the tokens.
|
||||
|
||||
By default, the organization attributes are not included in the organization claim. To include the attributes in the claim, edit the mapper and enable *Add organization attributes*.
|
||||
NOTE: By default, the organization id and attributes are not included in the organization claim. To include them, edit the mapper and enable the *Add organization id* and *Add organization attributes* options, respectively.
|
||||
|
||||
.Including attributes in the organization claim
|
||||
image:images/organizations-add-org-attrs-in-claim.png[alt="Including attributes in the organization claim"]
|
||||
|
|
|
@ -3246,6 +3246,8 @@ temporaryAdmin=Temporary admin user account. Ensure it is replaced with a perman
|
|||
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.
|
||||
addOrganizationId.label=Add organization id
|
||||
addOrganizationId.help=If enabled, the organization id will be available for each organization mapped to the token.
|
||||
identityProviderUnlink=Unlink identity provider?
|
||||
identityProviderUnlinkConfirm=Are you sure you want to unlink this identity provider?
|
||||
disableConfirmUserTitle=Disable user?
|
||||
|
|
|
@ -56,6 +56,7 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
|
||||
public static final String PROVIDER_ID = "oidc-organization-membership-mapper";
|
||||
public static final String ADD_ORGANIZATION_ATTRIBUTES = "addOrganizationAttributes";
|
||||
public static final String ADD_ORGANIZATION_ID = "addOrganizationId";
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
|
@ -77,6 +78,13 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
property.setDefaultValue(Boolean.FALSE.toString());
|
||||
property.setHelpText(ADD_ORGANIZATION_ATTRIBUTES + ".help");
|
||||
properties.add(property);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ADD_ORGANIZATION_ID);
|
||||
property.setLabel(ADD_ORGANIZATION_ID + ".label");
|
||||
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||
property.setDefaultValue(Boolean.FALSE.toString());
|
||||
property.setHelpText(ADD_ORGANIZATION_ID + ".help");
|
||||
properties.add(property);
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -137,7 +145,7 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
}
|
||||
|
||||
if (!OIDCAttributeMapperHelper.isMultivalued(model)) {
|
||||
return organizations.get(0).getName();
|
||||
return organizations.get(0).getAlias();
|
||||
}
|
||||
|
||||
Map<String, Map<String, Object>> value = new HashMap<>();
|
||||
|
@ -147,13 +155,16 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> attributes = Map.of();
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
if (isAddOrganizationId(model)) {
|
||||
claims.put(OAuth2Constants.ORGANIZATION_ID, o.getId());
|
||||
}
|
||||
if (isAddOrganizationAttributes(model)) {
|
||||
attributes = new HashMap<>(o.getAttributes());
|
||||
claims.putAll(o.getAttributes());
|
||||
}
|
||||
|
||||
value.put(o.getAlias(), attributes);
|
||||
value.put(o.getAlias(), claims);
|
||||
}
|
||||
|
||||
if (value.isEmpty()) {
|
||||
|
@ -181,9 +192,10 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
|
||||
if (!OIDCAttributeMapperHelper.isMultivalued(copy)) {
|
||||
config.put(ADD_ORGANIZATION_ATTRIBUTES, Boolean.FALSE.toString());
|
||||
config.put(ADD_ORGANIZATION_ID, Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
if (isAddOrganizationAttributes(copy)) {
|
||||
if (isAddOrganizationAttributes(copy) || isAddOrganizationId(copy)) {
|
||||
config.put(JSON_TYPE, "JSON");
|
||||
}
|
||||
|
||||
|
@ -208,6 +220,10 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
|||
return Boolean.parseBoolean(model.getConfig().getOrDefault(ADD_ORGANIZATION_ATTRIBUTES, Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
private boolean isAddOrganizationId(ProtocolMapperModel model) {
|
||||
return Boolean.parseBoolean(model.getConfig().getOrDefault(ADD_ORGANIZATION_ID, Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean introspectionEndpoint) {
|
||||
ProtocolMapperModel mapper = new ProtocolMapperModel();
|
||||
mapper.setName(name);
|
||||
|
|
|
@ -20,6 +20,7 @@ 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.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
@ -452,6 +453,47 @@ public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest
|
|||
assertThat(organizations.get(organizationName).keySet().isEmpty(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testIncludeOrganizationId() throws Exception {
|
||||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource organization = testRealm().organizations().get(orgRep.getId());
|
||||
addMember(organization);
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ID, 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, String>> organizations = (Map<String, Map<String, String>>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.keySet(), hasItem(organizationName));
|
||||
assertThat(organizations.get(organizationName).keySet(), hasItem("id"));
|
||||
assertThat(organizations.get(organizationName).get("id"), equalTo(orgRep.getId()));
|
||||
|
||||
// when id is added to tokens, the claim type is a json regardless of the value set in the config
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ID, 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, String>>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||
assertThat(organizations.keySet(), hasItem(organizationName));
|
||||
assertThat(organizations.get(organizationName).keySet(), hasItem("id"));
|
||||
assertThat(organizations.get(organizationName).get("id"), equalTo(orgRep.getId()));
|
||||
|
||||
// disabling the attribute should result in no ids in the claims.
|
||||
setMapperConfig(OrganizationMembershipMapper.ADD_ORGANIZATION_ID, 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, 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", true);
|
||||
|
|
Loading…
Reference in a new issue