KEYCLOAK-12001: Audience support for SAML clients
This commit is contained in:
parent
d8e450719b
commit
1989483401
7 changed files with 473 additions and 6 deletions
|
@ -458,7 +458,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||
assertion.addStatement(attributeStatement);
|
||||
}
|
||||
|
||||
samlModel = transformLoginResponse(loginResponseMappers, samlModel, session, userSession, clientSession);
|
||||
samlModel = transformLoginResponse(loginResponseMappers, samlModel, session, userSession, clientSessionCtx);
|
||||
samlDocument = builder.buildDocument(samlModel);
|
||||
} catch (Exception e) {
|
||||
logger.error("failed", e);
|
||||
|
@ -528,13 +528,14 @@ public class SamlProtocol implements LoginProtocol {
|
|||
return attributeStatement;
|
||||
}
|
||||
|
||||
public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers, ResponseType response, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
|
||||
public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers, ResponseType response,
|
||||
KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
|
||||
for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) {
|
||||
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession);
|
||||
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSessionCtx);
|
||||
}
|
||||
|
||||
for (Iterator<SamlAuthenticationPreprocessor> it = SamlSessionUtils.getSamlAuthenticationPreprocessorIterator(session); it.hasNext(); ) {
|
||||
response = (ResponseType) it.next().beforeSendingResponse(response, clientSession);
|
||||
response = (ResponseType) it.next().beforeSendingResponse(response, clientSessionCtx.getClientSession());
|
||||
}
|
||||
|
||||
return response;
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2019 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.saml.mappers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.dom.saml.v2.assertion.AudienceRestrictionType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
/**
|
||||
* SAML mapper to add a audience restriction into the assertion, to another
|
||||
* client (clientId) or to a custom URI. Only one URI is added, clientId
|
||||
* has preference over the custom value (the class maps OIDC behavior).
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class SAMLAudienceProtocolMapper extends AbstractSAMLProtocolMapper implements SAMLLoginResponseMapper {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(SAMLAudienceProtocolMapper.class);
|
||||
|
||||
public static final String PROVIDER_ID = "saml-audience-mapper";
|
||||
|
||||
public static final String AUDIENCE_CATEGORY = "Audience mapper";
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
public static final String INCLUDED_CLIENT_AUDIENCE = "included.client.audience";
|
||||
private static final String INCLUDED_CLIENT_AUDIENCE_LABEL = "included.client.audience.label";
|
||||
private static final String INCLUDED_CLIENT_AUDIENCE_HELP_TEXT = "included.client.audience.tooltip";
|
||||
|
||||
public static final String INCLUDED_CUSTOM_AUDIENCE = "included.custom.audience";
|
||||
private static final String INCLUDED_CUSTOM_AUDIENCE_LABEL = "included.custom.audience.label";
|
||||
private static final String INCLUDED_CUSTOM_AUDIENCE_HELP_TEXT = "included.custom.audience.tooltip";
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(INCLUDED_CLIENT_AUDIENCE);
|
||||
property.setLabel(INCLUDED_CLIENT_AUDIENCE_LABEL);
|
||||
property.setHelpText(INCLUDED_CLIENT_AUDIENCE_HELP_TEXT);
|
||||
property.setType(ProviderConfigProperty.CLIENT_LIST_TYPE);
|
||||
configProperties.add(property);
|
||||
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(INCLUDED_CUSTOM_AUDIENCE);
|
||||
property.setLabel(INCLUDED_CUSTOM_AUDIENCE_LABEL);
|
||||
property.setHelpText(INCLUDED_CUSTOM_AUDIENCE_HELP_TEXT);
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Audience";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return AUDIENCE_CATEGORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Add specified audience to the audience conditions in the assertion.";
|
||||
}
|
||||
|
||||
protected static AudienceRestrictionType locateAudienceRestriction(ResponseType response) {
|
||||
try {
|
||||
return response.getAssertions().get(0).getAssertion().getConditions().getConditions()
|
||||
.stream()
|
||||
.filter(AudienceRestrictionType.class::isInstance)
|
||||
.map(AudienceRestrictionType.class::cast)
|
||||
.findFirst().orElse(null);
|
||||
} catch (NullPointerException | IndexOutOfBoundsException e) {
|
||||
logger.warn("Invalid SAML ResponseType to add the audience restriction", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseType transformLoginResponse(ResponseType response,
|
||||
ProtocolMapperModel mappingModel, KeycloakSession session,
|
||||
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
|
||||
// read configuration as in OIDC (first clientId, then custom)
|
||||
String audience = mappingModel.getConfig().get(INCLUDED_CLIENT_AUDIENCE);
|
||||
if (audience == null || audience.isEmpty()) {
|
||||
audience = mappingModel.getConfig().get(INCLUDED_CUSTOM_AUDIENCE);
|
||||
}
|
||||
// locate the first condition that has an audience restriction
|
||||
if (audience != null && !audience.isEmpty()) {
|
||||
AudienceRestrictionType aud = locateAudienceRestriction(response);
|
||||
if (aud != null) {
|
||||
logger.debugf("adding audience: %s", audience);
|
||||
try {
|
||||
aud.addAudience(URI.create(audience));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warnf(e, "Invalid URI syntax for audience: %s", audience);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2019 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.saml.mappers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.dom.saml.v2.assertion.AudienceRestrictionType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
/**
|
||||
* SAML audience resolve mapper. The mapper adds all client_ids of \"allowed\"
|
||||
* clients to the audience conditions in the assertion. Allowed client means
|
||||
* any SAML client for which user has at least one client role.
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class SAMLAudienceResolveProtocolMapper extends AbstractSAMLProtocolMapper implements SAMLLoginResponseMapper {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(SAMLAudienceResolveProtocolMapper.class);
|
||||
|
||||
public static final String PROVIDER_ID = "saml-audience-resolve-mapper";
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Audience Resolve";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return SAMLAudienceProtocolMapper.AUDIENCE_CATEGORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Adds all client_ids of \"allowed\" clients to the audience conditions in the assertion. " +
|
||||
"Allowed client means any SAML client for which user has at least one client role";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseType transformLoginResponse(ResponseType response,
|
||||
ProtocolMapperModel mappingModel, KeycloakSession session,
|
||||
UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
|
||||
// get the audience restriction
|
||||
AudienceRestrictionType aud = SAMLAudienceProtocolMapper.locateAudienceRestriction(response);
|
||||
if (aud != null) {
|
||||
// get all the roles the user has and calculate the clientIds to add
|
||||
Set<RoleModel> roles = clientSessionCtx.getRoles();
|
||||
Set<String> audiences = new HashSet<>();
|
||||
// add as audience any SAML clientId with role included (same as OIDC)
|
||||
for (RoleModel role : roles) {
|
||||
logger.tracef("Managing role: %s", role.getName());
|
||||
if (role.isClientRole()) {
|
||||
ClientModel app = (ClientModel) role.getContainer();
|
||||
// only adding SAML clients that are not this clientId (which is added by default)
|
||||
if (SamlProtocol.LOGIN_PROTOCOL.equals(app.getProtocol()) &&
|
||||
!app.getClientId().equals(clientSessionCtx.getClientSession().getClient().getClientId())) {
|
||||
audiences.add(app.getClientId());
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debugf("Calculated audiences to add: %s", audiences);
|
||||
// add the audiences
|
||||
for (String audience : audiences) {
|
||||
try {
|
||||
aud.addAudience(URI.create(audience));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warnf(e, "Invalid URI syntax for audience: %s", audience);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
package org.keycloak.protocol.saml.mappers;
|
||||
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
@ -29,6 +29,7 @@ import org.keycloak.models.UserSessionModel;
|
|||
*/
|
||||
public interface SAMLLoginResponseMapper {
|
||||
|
||||
|
||||
ResponseType transformLoginResponse(ResponseType response, ProtocolMapperModel mappingModel, KeycloakSession session,
|
||||
UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
|
||||
UserSessionModel userSession, ClientSessionContext clientSessionCtx);
|
||||
}
|
||||
|
|
|
@ -41,3 +41,5 @@ org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper
|
|||
org.keycloak.protocol.oidc.mappers.SHA256PairwiseSubMapper
|
||||
org.keycloak.protocol.docker.mapper.AllowAllDockerProtocolMapper
|
||||
org.keycloak.protocol.oidc.mappers.ScriptBasedOIDCProtocolMapper
|
||||
org.keycloak.protocol.saml.mappers.SAMLAudienceProtocolMapper
|
||||
org.keycloak.protocol.saml.mappers.SAMLAudienceResolveProtocolMapper
|
||||
|
|
|
@ -125,4 +125,9 @@ public class ClientAttributeUpdater extends ServerResourceUpdater<ClientAttribut
|
|||
rep.setAdminUrl(adminUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientAttributeUpdater addDefaultClientScope(String clientScope) {
|
||||
rep.getDefaultClientScopes().add(clientScope);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright 2019 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.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.ws.rs.core.Response;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.dom.saml.v2.assertion.AudienceRestrictionType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.protocol.saml.mappers.SAMLAudienceProtocolMapper;
|
||||
import org.keycloak.protocol.saml.mappers.SAMLAudienceResolveProtocolMapper;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import static org.keycloak.testsuite.saml.AbstractSamlTest.REALM_NAME;
|
||||
import static org.keycloak.testsuite.saml.AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2;
|
||||
import static org.keycloak.testsuite.saml.RoleMapperTest.createSamlProtocolMapper;
|
||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||
import org.keycloak.testsuite.updaters.ProtocolMappersUpdater;
|
||||
import org.keycloak.testsuite.updaters.RoleScopeUpdater;
|
||||
import org.keycloak.testsuite.updaters.UserAttributeUpdater;
|
||||
import org.keycloak.testsuite.util.Matchers;
|
||||
import org.keycloak.testsuite.util.SamlClient;
|
||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class AudienceProtocolMappersTest extends AbstractSamlTest {
|
||||
|
||||
public static final String SAML_ASSERTION_CONSUMER_URL_EMPLOYEE_2 = AUTH_SERVER_SCHEME + "://localhost:" + (AUTH_SERVER_SSL_REQUIRED ? AUTH_SERVER_PORT : 8080) + "/employee2/";
|
||||
|
||||
private ProtocolMappersUpdater pmu;
|
||||
|
||||
@Before
|
||||
public void cleanMappersAndScopes() {
|
||||
this.pmu = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_EMPLOYEE_2).protocolMappers()
|
||||
.clear()
|
||||
.update();
|
||||
}
|
||||
|
||||
@After
|
||||
public void revertCleanMappersAndScopes() throws IOException {
|
||||
this.pmu.close();
|
||||
}
|
||||
|
||||
public void testExpectedAudiences(String... audiences) {
|
||||
SAMLDocumentHolder document = new SamlClientBuilder()
|
||||
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_EMPLOYEE_2, SAML_ASSERTION_CONSUMER_URL_EMPLOYEE_2, SamlClient.Binding.POST).build()
|
||||
.login().user(bburkeUser).build()
|
||||
.getSamlResponse(SamlClient.Binding.POST);
|
||||
|
||||
Assert.assertNotNull(document.getSamlObject());
|
||||
Assert.assertThat(document.getSamlObject(), Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
|
||||
Assert.assertNotNull(((ResponseType) document.getSamlObject()).getAssertions());
|
||||
Assert.assertThat(((ResponseType) document.getSamlObject()).getAssertions().size(), greaterThan(0));
|
||||
Assert.assertNotNull(((ResponseType) document.getSamlObject()).getAssertions().get(0));
|
||||
Assert.assertNotNull(((ResponseType) document.getSamlObject()).getAssertions().get(0).getAssertion());
|
||||
AudienceRestrictionType audience = ((ResponseType) document.getSamlObject())
|
||||
.getAssertions().get(0).getAssertion().getConditions().getConditions()
|
||||
.stream()
|
||||
.filter(AudienceRestrictionType.class::isInstance)
|
||||
.map(AudienceRestrictionType.class::cast)
|
||||
.findFirst().orElse(null);
|
||||
Assert.assertNotNull(audience);
|
||||
Assert.assertNotNull(audience.getAudience());
|
||||
List<String> values = audience.getAudience().stream().map(uri -> uri.toString()).collect(Collectors.toList());
|
||||
Assert.assertThat(values, containsInAnyOrder(audiences));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultAudience() throws Exception {
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomAudience() throws Exception {
|
||||
pmu.add(
|
||||
createSamlProtocolMapper(SAMLAudienceProtocolMapper.PROVIDER_ID,
|
||||
SAMLAudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, "https://test.com/test"
|
||||
)
|
||||
).update();
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, "https://test.com/test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientAudience() throws Exception {
|
||||
pmu.add(
|
||||
createSamlProtocolMapper(SAMLAudienceProtocolMapper.PROVIDER_ID,
|
||||
SAMLAudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE, SAML_CLIENT_ID_SALES_POST
|
||||
)
|
||||
).update();
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, SAML_CLIENT_ID_SALES_POST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientAndCustomAudience() throws Exception {
|
||||
pmu.add(
|
||||
createSamlProtocolMapper(SAMLAudienceProtocolMapper.PROVIDER_ID,
|
||||
SAMLAudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE, SAML_CLIENT_ID_SALES_POST,
|
||||
SAMLAudienceProtocolMapper.INCLUDED_CUSTOM_AUDIENCE, "https://test.com/test"
|
||||
)
|
||||
).update();
|
||||
// only client is expected because it works as the OIDC one (same labels used)
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, SAML_CLIENT_ID_SALES_POST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudienceResolveFullScope() throws Exception {
|
||||
pmu.add(createSamlProtocolMapper(SAMLAudienceResolveProtocolMapper.PROVIDER_ID)).update();
|
||||
// bburke in the saml realm belongs to three different SAML clients groups
|
||||
// "http://localhost:8280/employee/": [ "employee" ],
|
||||
// "http://localhost:8280/employee2/": [ "empl.oyee", "employee" ],
|
||||
// "http://localhost:8280/employee-role-mapping/": ["employee"]
|
||||
// this way it should contain the three apps by default
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, "http://localhost:8280/employee/", "http://localhost:8280/employee-role-mapping/");
|
||||
// remove one of the groups (employee) and check the employee audience is removed
|
||||
String employeeId = adminClient.realm(REALM_NAME).clients().findByClientId("http://localhost:8280/employee/").get(0).getId();
|
||||
Assert.assertNotNull(employeeId);
|
||||
try (RoleScopeUpdater rsc = UserAttributeUpdater.forUserByUsername(adminClient, REALM_NAME, bburkeUser.getUsername())
|
||||
.clientRoleScope(employeeId)
|
||||
.removeByName("employee")
|
||||
.update()) {
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, "http://localhost:8280/employee-role-mapping/");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudienceResolveNoFullScope() throws Exception {
|
||||
pmu.add(createSamlProtocolMapper(SAMLAudienceResolveProtocolMapper.PROVIDER_ID)).update();
|
||||
// remove full scope
|
||||
try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_EMPLOYEE_2)
|
||||
.setFullScopeAllowed(false)
|
||||
.update()) {
|
||||
// now only the same client should be in the audience
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2);
|
||||
|
||||
// add another client in the scope
|
||||
String employee2Id = adminClient.realm(REALM_NAME).clients().findByClientId("http://localhost:8280/employee2/").get(0).getId();
|
||||
Assert.assertNotNull(employee2Id);
|
||||
String employeeId = adminClient.realm(REALM_NAME).clients().findByClientId("http://localhost:8280/employee/").get(0).getId();
|
||||
Assert.assertNotNull(employeeId);
|
||||
List<RoleRepresentation> availables = adminClient.realm(REALM_NAME).clients().get(employee2Id).getScopeMappings().clientLevel(employeeId).listAvailable();
|
||||
Assert.assertThat(availables.size(), greaterThan(0));
|
||||
// assign scope to only employee2 (employee-role-mapping should not be there)
|
||||
try (RoleScopeUpdater ru = cau.clientRoleScope(employeeId)
|
||||
.add(availables.get(0))
|
||||
.update()) {
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, "http://localhost:8280/employee/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudienceResolveNoFullScopeClientScopes() throws Exception {
|
||||
// create the mapper using a client scope
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName("audience-mapper-test-client-scope");
|
||||
clientScope.setProtocol("saml");
|
||||
clientScope.setProtocolMappers(Collections.singletonList(createSamlProtocolMapper(SAMLAudienceResolveProtocolMapper.PROVIDER_ID)));
|
||||
Response res = adminClient.realm(REALM_NAME).clientScopes().create(clientScope);
|
||||
Assert.assertEquals(Response.Status.CREATED.getStatusCode(), res.getStatus());
|
||||
String clientScopeId = ApiUtil.getCreatedId(res);
|
||||
|
||||
try {
|
||||
// add a mapping to the client scope to employee2.employee role (this way employee should be in the audience)
|
||||
String employee2Id = adminClient.realm(REALM_NAME).clients().findByClientId("http://localhost:8280/employee2/").get(0).getId();
|
||||
Assert.assertNotNull(employee2Id);
|
||||
String employeeId = adminClient.realm(REALM_NAME).clients().findByClientId("http://localhost:8280/employee/").get(0).getId();
|
||||
Assert.assertNotNull(employeeId);
|
||||
List<RoleRepresentation> availables = adminClient.realm(REALM_NAME).clientScopes().get(clientScopeId).getScopeMappings().clientLevel(employeeId).listAvailable();
|
||||
Assert.assertThat(availables.size(), greaterThan(0));
|
||||
adminClient.realm(REALM_NAME).clientScopes().get(clientScopeId).getScopeMappings().clientLevel(employeeId).add(availables);
|
||||
|
||||
// remove full scope and add the client scope
|
||||
try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_EMPLOYEE_2)
|
||||
.setFullScopeAllowed(false)
|
||||
.addDefaultClientScope("audience-mapper-test-client-scope")
|
||||
.update()) {
|
||||
this.testExpectedAudiences(SAML_CLIENT_ID_EMPLOYEE_2, "http://localhost:8280/employee/");
|
||||
}
|
||||
} finally {
|
||||
adminClient.realm(REALM_NAME).clientScopes().get(clientScopeId).remove();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue