Map organization metadata when issuing tokens for OIDC clients acting on behalf of an organization member
Closes #27993 Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
parent
e7bc796553
commit
fa1571f231
13 changed files with 209 additions and 17 deletions
|
@ -92,6 +92,8 @@ public interface OAuth2Constants {
|
||||||
String SCOPE_ADDRESS = "address";
|
String SCOPE_ADDRESS = "address";
|
||||||
String SCOPE_PHONE = "phone";
|
String SCOPE_PHONE = "phone";
|
||||||
|
|
||||||
|
String ORGANIZATION = "organization";
|
||||||
|
|
||||||
String UI_LOCALES_PARAM = "ui_locales";
|
String UI_LOCALES_PARAM = "ui_locales";
|
||||||
|
|
||||||
String PROMPT = "prompt";
|
String PROMPT = "prompt";
|
||||||
|
|
|
@ -29,7 +29,7 @@ import jakarta.persistence.Table;
|
||||||
@Table(name="ORGANIZATION")
|
@Table(name="ORGANIZATION")
|
||||||
@Entity
|
@Entity
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name="getByRealm", query="select o.id from OrganizationEntity o where o.realmId = :realmId")
|
@NamedQuery(name="getByRealm", query="select o from OrganizationEntity o where o.realmId = :realmId")
|
||||||
})
|
})
|
||||||
public class OrganizationEntity {
|
public class OrganizationEntity {
|
||||||
|
|
||||||
|
|
|
@ -41,11 +41,9 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
private final GroupProvider groupProvider;
|
private final GroupProvider groupProvider;
|
||||||
private final KeycloakSession session;
|
|
||||||
private final UserProvider userProvider;
|
private final UserProvider userProvider;
|
||||||
|
|
||||||
public JpaOrganizationProvider(KeycloakSession session) {
|
public JpaOrganizationProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
|
||||||
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||||
groupProvider = session.groups();
|
groupProvider = session.groups();
|
||||||
userProvider = session.users();
|
userProvider = session.users();
|
||||||
|
@ -63,7 +61,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
|
|
||||||
return new OrganizationAdapter(entity, session);
|
return new OrganizationAdapter(realm, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,11 +116,11 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
@Override
|
@Override
|
||||||
public Stream<OrganizationModel> getOrganizationsStream(RealmModel realm) {
|
public Stream<OrganizationModel> getOrganizationsStream(RealmModel realm) {
|
||||||
throwExceptionIfRealmIsNull(realm);
|
throwExceptionIfRealmIsNull(realm);
|
||||||
TypedQuery<String> query = em.createNamedQuery("getByRealm", String.class);
|
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByRealm", OrganizationEntity.class);
|
||||||
|
|
||||||
query.setParameter("realmId", realm.getId());
|
query.setParameter("realmId", realm.getId());
|
||||||
|
|
||||||
return closing(query.getResultStream().map(id -> getAdapter(realm, id)));
|
return closing(query.getResultStream().map(entity -> new OrganizationAdapter(realm, entity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -193,7 +191,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
throw new ModelException("Organization [" + entity.getId() + " does not belong to realm [" + realm.getId() + "]");
|
throw new ModelException("Organization [" + entity.getId() + " does not belong to realm [" + realm.getId() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OrganizationAdapter(entity, session);
|
return new OrganizationAdapter(realm, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupModel createOrganizationGroup(RealmModel realm, String name) {
|
private GroupModel createOrganizationGroup(RealmModel realm, String name) {
|
||||||
|
@ -209,7 +207,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
throw new ModelException("A group with the same name already exist and it is bound to different organization");
|
throw new ModelException("A group with the same name already exist and it is bound to different organization");
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupProvider.createGroup(realm, KeycloakModelUtils.generateId(), groupName);
|
return groupProvider.createGroup(realm, groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCanonicalGroupName(String name) {
|
private String getCanonicalGroupName(String name) {
|
||||||
|
|
|
@ -17,19 +17,19 @@
|
||||||
|
|
||||||
package org.keycloak.organization.jpa;
|
package org.keycloak.organization.jpa;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.OrganizationModel;
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.jpa.JpaModel;
|
import org.keycloak.models.jpa.JpaModel;
|
||||||
import org.keycloak.models.jpa.entities.OrganizationEntity;
|
import org.keycloak.models.jpa.entities.OrganizationEntity;
|
||||||
|
|
||||||
public final class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> {
|
public final class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> {
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
private final OrganizationEntity entity;
|
private final OrganizationEntity entity;
|
||||||
private final KeycloakSession session;
|
|
||||||
|
|
||||||
public OrganizationAdapter(OrganizationEntity entity, KeycloakSession session) {
|
public OrganizationAdapter(RealmModel realm, OrganizationEntity entity) {
|
||||||
|
this.realm = realm;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
this.session = session;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -37,8 +37,9 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRealm() {
|
@Override
|
||||||
return entity.getRealmId();
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getGroupId() {
|
String getGroupId() {
|
||||||
|
@ -69,6 +70,9 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
||||||
.append("name=")
|
.append("name=")
|
||||||
.append(getName())
|
.append(getName())
|
||||||
.append(",")
|
.append(",")
|
||||||
|
.append("realm=")
|
||||||
|
.append(getRealm().getName())
|
||||||
|
.append(",")
|
||||||
.append("groupId=")
|
.append("groupId=")
|
||||||
.append(getGroupId()).toString();
|
.append(getGroupId()).toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,6 @@ public interface OrganizationModel {
|
||||||
void setName(String name);
|
void setName(String name);
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
RealmModel getRealm();
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.protocol.oidc.mappers.AddressMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper;
|
import org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper;
|
import org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.FullNameMapper;
|
import org.keycloak.protocol.oidc.mappers.FullNameMapper;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.OrganizationMembershipMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
|
import org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
|
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
|
||||||
|
@ -87,6 +88,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
public static final String AUDIENCE_RESOLVE = "audience resolve";
|
public static final String AUDIENCE_RESOLVE = "audience resolve";
|
||||||
public static final String ALLOWED_WEB_ORIGINS = "allowed web origins";
|
public static final String ALLOWED_WEB_ORIGINS = "allowed web origins";
|
||||||
public static final String ACR = "acr loa level";
|
public static final String ACR = "acr loa level";
|
||||||
|
public static final String ORGANIZATION = "organization";
|
||||||
// microprofile-jwt claims
|
// microprofile-jwt claims
|
||||||
public static final String UPN = "upn";
|
public static final String UPN = "upn";
|
||||||
public static final String GROUPS = "groups";
|
public static final String GROUPS = "groups";
|
||||||
|
@ -102,6 +104,7 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
public static final String PHONE_SCOPE_CONSENT_TEXT = "${phoneScopeConsentText}";
|
public static final String PHONE_SCOPE_CONSENT_TEXT = "${phoneScopeConsentText}";
|
||||||
public static final String OFFLINE_ACCESS_SCOPE_CONSENT_TEXT = Constants.OFFLINE_ACCESS_SCOPE_CONSENT_TEXT;
|
public static final String OFFLINE_ACCESS_SCOPE_CONSENT_TEXT = Constants.OFFLINE_ACCESS_SCOPE_CONSENT_TEXT;
|
||||||
public static final String ROLES_SCOPE_CONSENT_TEXT = "${rolesScopeConsentText}";
|
public static final String ROLES_SCOPE_CONSENT_TEXT = "${rolesScopeConsentText}";
|
||||||
|
public static final String ORGANIZATION_SCOPE_CONSENT_TEXT = "${organizationScopeConsentText}";
|
||||||
|
|
||||||
public static final String CONFIG_LEGACY_LOGOUT_REDIRECT_URI = "legacy-logout-redirect-uri";
|
public static final String CONFIG_LEGACY_LOGOUT_REDIRECT_URI = "legacy-logout-redirect-uri";
|
||||||
public static final String SUPPRESS_LOGOUT_CONFIRMATION_SCREEN = "suppress-logout-confirmation-screen";
|
public static final String SUPPRESS_LOGOUT_CONFIRMATION_SCREEN = "suppress-logout-confirmation-screen";
|
||||||
|
@ -295,6 +298,17 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||||
addWebOriginsClientScope(newRealm);
|
addWebOriginsClientScope(newRealm);
|
||||||
addMicroprofileJWTClientScope(newRealm);
|
addMicroprofileJWTClientScope(newRealm);
|
||||||
addAcrClientScope(newRealm);
|
addAcrClientScope(newRealm);
|
||||||
|
|
||||||
|
if (Profile.isFeatureEnabled(Profile.Feature.ORGANIZATION)) {
|
||||||
|
ClientScopeModel organizationScope = newRealm.addClientScope(OAuth2Constants.ORGANIZATION);
|
||||||
|
organizationScope.setDescription("Additional claims about the organization a subject belongs to");
|
||||||
|
organizationScope.setDisplayOnConsentScreen(true);
|
||||||
|
organizationScope.setConsentScreenText(ORGANIZATION_SCOPE_CONSENT_TEXT);
|
||||||
|
organizationScope.setIncludeInTokenScope(true);
|
||||||
|
organizationScope.setProtocol(getId());
|
||||||
|
organizationScope.addProtocolMapper(OrganizationMembershipMapper.create(ORGANIZATION, true, true, true));
|
||||||
|
newRealm.addDefaultClientScope(organizationScope, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.protocol.oidc.mappers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.models.ClientSessionContext;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
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";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
List<ProviderConfigProperty> properties = new ArrayList<>();
|
||||||
|
OIDCAttributeMapperHelper.addIncludeInTokensConfig(properties, OrganizationMembershipMapper.class);
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Organization Membership";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return TOKEN_MAPPER_CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Map user Organization membership";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
|
||||||
|
|
||||||
|
RealmModel realm = keycloakSession.getContext().getRealm();
|
||||||
|
UserModel user = userSession.getUser();
|
||||||
|
OrganizationProvider organizationProvider = keycloakSession.getProvider(OrganizationProvider.class);
|
||||||
|
OrganizationModel organization = organizationProvider.getOrganizationByMember(realm, user);
|
||||||
|
|
||||||
|
if (organization != null) {
|
||||||
|
Map<String, Map<String, Object>> claim = new HashMap<>();
|
||||||
|
claim.put(organization.getName(), Map.of());
|
||||||
|
token.getOtherClaims().put(OAuth2Constants.ORGANIZATION, claim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProtocolMapperModel create(String name, boolean accessToken, boolean idToken, boolean introspectionEndpoint) {
|
||||||
|
ProtocolMapperModel mapper = new ProtocolMapperModel();
|
||||||
|
mapper.setName(name);
|
||||||
|
mapper.setProtocolMapper(PROVIDER_ID);
|
||||||
|
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
Map<String, String> config = new HashMap<>();
|
||||||
|
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");
|
||||||
|
mapper.setConfig(config);
|
||||||
|
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported(Config.Scope config) {
|
||||||
|
return Profile.isFeatureEnabled(Profile.Feature.ORGANIZATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ org.keycloak.protocol.oidc.mappers.RoleNameMapper
|
||||||
org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
|
org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
|
||||||
org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
|
org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
|
||||||
org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper
|
org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper
|
||||||
|
org.keycloak.protocol.oidc.mappers.OrganizationMembershipMapper
|
||||||
org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper
|
org.keycloak.protocol.oidc.mappers.AudienceResolveProtocolMapper
|
||||||
org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper
|
org.keycloak.protocol.oidc.mappers.AllowedWebOriginsProtocolMapper
|
||||||
org.keycloak.protocol.oidc.mappers.AcrProtocolMapper
|
org.keycloak.protocol.oidc.mappers.AcrProtocolMapper
|
||||||
|
|
|
@ -27,14 +27,19 @@ import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.admin.Users;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
||||||
|
|
||||||
|
protected String organizationName = "neworg";
|
||||||
|
protected String memberEmail = "jdoe@neworg.org";
|
||||||
|
protected String memberPassword = "password";
|
||||||
|
|
||||||
protected OrganizationRepresentation createOrganization() {
|
protected OrganizationRepresentation createOrganization() {
|
||||||
return createOrganization("neworg");
|
return createOrganization(organizationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OrganizationRepresentation createOrganization(String name) {
|
protected OrganizationRepresentation createOrganization(String name) {
|
||||||
|
@ -56,7 +61,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserRepresentation addMember(OrganizationResource organization) {
|
protected UserRepresentation addMember(OrganizationResource organization) {
|
||||||
return addMember(organization, "jdoe@neworg.org");
|
return addMember(organization, memberEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserRepresentation addMember(OrganizationResource organization, String email) {
|
protected UserRepresentation addMember(OrganizationResource organization, String email) {
|
||||||
|
@ -64,6 +69,8 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
||||||
|
|
||||||
expected.setEmail(email);
|
expected.setEmail(email);
|
||||||
expected.setUsername(expected.getEmail());
|
expected.setUsername(expected.getEmail());
|
||||||
|
expected.setEnabled(true);
|
||||||
|
Users.setPasswordFor(expected, memberPassword);
|
||||||
|
|
||||||
try (Response response = organization.members().addMember(expected)) {
|
try (Response response = organization.members().addMember(expected)) {
|
||||||
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
|
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.organization.admin;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.TokenVerifier;
|
||||||
|
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||||
|
import org.keycloak.common.Profile.Feature;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
|
||||||
|
|
||||||
|
@EnableFeature(Feature.ORGANIZATION)
|
||||||
|
public class OrganizationOIDCProtocolMapperTest extends AbstractOrganizationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClaim() throws Exception {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
addMember(organization);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> claim = (Map<String, Object>) accessToken.getOtherClaims().get(OAuth2Constants.ORGANIZATION);
|
||||||
|
assertThat(claim, notNullValue());
|
||||||
|
assertThat(claim.get(organizationName), notNullValue());
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ public class OrganizationTest extends AbstractOrganizationTest {
|
||||||
public void testUpdate() {
|
public void testUpdate() {
|
||||||
OrganizationRepresentation expected = createOrganization();
|
OrganizationRepresentation expected = createOrganization();
|
||||||
|
|
||||||
assertEquals("neworg", expected.getName());
|
assertEquals(organizationName, expected.getName());
|
||||||
expected.setName("acme");
|
expected.setName("acme");
|
||||||
|
|
||||||
OrganizationResource organization = testRealm().organizations().get(expected.getId());
|
OrganizationResource organization = testRealm().organizations().get(expected.getId());
|
||||||
|
|
|
@ -76,6 +76,7 @@ phoneScopeConsentText=Phone number
|
||||||
offlineAccessScopeConsentText=Offline Access
|
offlineAccessScopeConsentText=Offline Access
|
||||||
samlRoleListScopeConsentText=My Roles
|
samlRoleListScopeConsentText=My Roles
|
||||||
rolesScopeConsentText=User roles
|
rolesScopeConsentText=User roles
|
||||||
|
organizationScopeConsentText=Organization
|
||||||
|
|
||||||
role_admin=Admin
|
role_admin=Admin
|
||||||
role_realm-admin=Realm Admin
|
role_realm-admin=Realm Admin
|
||||||
|
|
|
@ -116,6 +116,7 @@ phoneScopeConsentText=Phone number
|
||||||
offlineAccessScopeConsentText=Offline Access
|
offlineAccessScopeConsentText=Offline Access
|
||||||
samlRoleListScopeConsentText=My Roles
|
samlRoleListScopeConsentText=My Roles
|
||||||
rolesScopeConsentText=User roles
|
rolesScopeConsentText=User roles
|
||||||
|
organizationScopeConsentText=Organization
|
||||||
|
|
||||||
restartLoginTooltip=Restart login
|
restartLoginTooltip=Restart login
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue