8000 Add option to include the organization id in the organization claims by sguilhen · Pull Request #33495 · keycloak/keycloak · GitHub
[go: up one dir, main page]

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
1 change: 1 addition & 0 deletions core/src/main/java/org/keycloak/OAuth2Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public interface OAuth2Constants {
String SCOPE_PHONE = "phone";

String ORGANIZATION = "organization";
String ORGANIZATION_ID = "id";

String UI_LOCALES_PARAM = "ui_locales";

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
Expand All @@ -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"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -77,6 +78,13 @@ public List<ProviderConfigProperty> getConfigProperties() {
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;
}

Expand Down Expand Up @@ -137,7 +145,7 @@ private Object resolveValue(ProtocolMapperModel model, List<OrganizationModel> o
}

if (!OIDCAttributeMapperHelper.isMultivalued(model)) {
return organizations.get(0).getName();
return organizations.get(0).getAlias();
}

Map<String, Map<String, Object>> value = new HashMap<>();
Expand All @@ -147,13 +155,16 @@ private Object resolveValue(ProtocolMapperModel model, List<OrganizationModel> o
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()) {
Expand Down Expand Up @@ -181,9 +192,10 @@ public ProtocolMapperModel getEffectiveModel(KeycloakSession session, RealmModel

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

Expand All @@ -208,6 +220,10 @@ private boolean isAddOrganizationAttributes(ProtocolMapperModel model) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -452,6 +453,47 @@ public void testIncludeOrganizationAttributes() throws Exception {
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 599F (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);
Expand Down
0