Support for resolving organization based on the organization scope
Closes #31438 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
310824cc2b
commit
96acc62c00
30 changed files with 666 additions and 253 deletions
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
* 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.models;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class ClientScopeDecorator implements ClientScopeModel {
|
||||||
|
|
||||||
|
private final ClientScopeModel delegate;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public ClientScopeDecorator(ClientScopeModel delegate, String name) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return delegate.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return delegate.getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
delegate.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return delegate.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
delegate.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol() {
|
||||||
|
return delegate.getProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProtocol(String protocol) {
|
||||||
|
delegate.setProtocol(protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, String value) {
|
||||||
|
delegate.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
delegate.removeAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAttribute(String name) {
|
||||||
|
return delegate.getAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
return delegate.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisplayOnConsentScreen() {
|
||||||
|
return delegate.isDisplayOnConsentScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDisplayOnConsentScreen(boolean displayOnConsentScreen) {
|
||||||
|
delegate.setDisplayOnConsentScreen(displayOnConsentScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConsentScreenText() {
|
||||||
|
return delegate.getConsentScreenText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConsentScreenText(String consentScreenText) {
|
||||||
|
delegate.setConsentScreenText(consentScreenText);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGuiOrder() {
|
||||||
|
return delegate.getGuiOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGuiOrder(String guiOrder) {
|
||||||
|
delegate.setGuiOrder(guiOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIncludeInTokenScope() {
|
||||||
|
return delegate.isIncludeInTokenScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIncludeInTokenScope(boolean includeInTokenScope) {
|
||||||
|
delegate.setIncludeInTokenScope(includeInTokenScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDynamicScope() {
|
||||||
|
return delegate.isDynamicScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIsDynamicScope(boolean isDynamicScope) {
|
||||||
|
delegate.setIsDynamicScope(isDynamicScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDynamicScopeRegexp() {
|
||||||
|
return delegate.getDynamicScopeRegexp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
|
||||||
|
return delegate.getProtocolMappersStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||||
|
return delegate.addProtocolMapper(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeProtocolMapper(ProtocolMapperModel mapping) {
|
||||||
|
delegate.removeProtocolMapper(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProtocolMapper(ProtocolMapperModel mapping) {
|
||||||
|
delegate.updateProtocolMapper(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||||
|
return delegate.getProtocolMapperById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||||
|
return delegate.getProtocolMapperByName(protocol, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getScopeMappingsStream() {
|
||||||
|
return delegate.getScopeMappingsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<RoleModel> getRealmScopeMappingsStream() {
|
||||||
|
return delegate.getRealmScopeMappingsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addScopeMapping(RoleModel role) {
|
||||||
|
delegate.addScopeMapping(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteScopeMapping(RoleModel role) {
|
||||||
|
delegate.deleteScopeMapping(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasDirectScope(RoleModel role) {
|
||||||
|
return delegate.hasDirectScope(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasScope(RoleModel role) {
|
||||||
|
return delegate.hasScope(role);
|
||||||
|
}
|
||||||
|
}
|
|
@ -232,4 +232,14 @@ public interface OrganizationProvider extends Provider {
|
||||||
* @return long Number of organizations
|
* @return long Number of organizations
|
||||||
*/
|
*/
|
||||||
long count();
|
long count();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an {@link OrganizationModel} with the given {@code alias}.
|
||||||
|
*
|
||||||
|
* @param alias the alias
|
||||||
|
* @return the organization
|
||||||
|
*/
|
||||||
|
default OrganizationModel getByAlias(String alias) {
|
||||||
|
return getAllStream(Map.of(OrganizationModel.ALIAS, alias), 0, 1).findAny().orElse(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1170,7 +1170,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
protected Response authenticationComplete() {
|
protected Response authenticationComplete() {
|
||||||
// attachSession(); // Session will be attached after requiredActions + consents are finished.
|
// attachSession(); // Session will be attached after requiredActions + consents are finished.
|
||||||
AuthenticationManager.setClientScopesInSession(authenticationSession);
|
AuthenticationManager.setClientScopesInSession(session, authenticationSession);
|
||||||
|
|
||||||
String nextRequiredAction = nextRequiredAction();
|
String nextRequiredAction = nextRequiredAction();
|
||||||
if (nextRequiredAction != null) {
|
if (nextRequiredAction != null) {
|
||||||
|
|
|
@ -283,7 +283,7 @@ public class PolicyEvaluationService {
|
||||||
userSession = new UserSessionManager(keycloakSession).createUserSession(authSession.getParentSession().getId(), realm, userModel,
|
userSession = new UserSessionManager(keycloakSession).createUserSession(authSession.getParentSession().getId(), realm, userModel,
|
||||||
userModel.getUsername(), "127.0.0.1", "passwd", false, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
userModel.getUsername(), "127.0.0.1", "passwd", false, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(keycloakSession, authSession);
|
||||||
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(keycloakSession, userSession, authSession);
|
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(keycloakSession, userSession, authSession);
|
||||||
|
|
||||||
accessToken = new TokenManager().createClientAccessToken(keycloakSession, realm, clientModel, userModel, userSession, clientSessionCtx);
|
accessToken = new TokenManager().createClientAccessToken(keycloakSession, realm, clientModel, userModel, userSession, clientSessionCtx);
|
||||||
|
|
|
@ -346,7 +346,7 @@ public class AuthorizationTokenService {
|
||||||
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(keycloakSession.getContext().getUri().getBaseUri(), realm.getName()));
|
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(keycloakSession.getContext().getUri().getBaseUri(), realm.getName()));
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(keycloakSession, authSession);
|
||||||
clientSessionCtx = TokenManager.attachAuthenticationSession(keycloakSession, userSessionModel, authSession);
|
clientSessionCtx = TokenManager.attachAuthenticationSession(keycloakSession, userSessionModel, authSession);
|
||||||
} else {
|
} else {
|
||||||
clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, keycloakSession);
|
clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, keycloakSession);
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.keycloak.organization.OrganizationProvider;
|
||||||
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareAuthenticationContextBean;
|
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareAuthenticationContextBean;
|
||||||
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareIdentityProviderBean;
|
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareIdentityProviderBean;
|
||||||
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareRealmBean;
|
import org.keycloak.organization.forms.login.freemarker.model.OrganizationAwareRealmBean;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
|
|
||||||
|
@ -63,7 +64,16 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OrganizationModel organization = resolveOrganization(session);
|
||||||
|
|
||||||
|
if (organization == null) {
|
||||||
initialChallenge(context);
|
initialChallenge(context);
|
||||||
|
} else {
|
||||||
|
// make sure the organization is set to the auth session to remember it when processing subsequent requests
|
||||||
|
AuthenticationSessionModel authSession = context.getAuthenticationSession();
|
||||||
|
authSession.setAuthNote(OrganizationModel.ORGANIZATION_ATTRIBUTE, organization.getId());
|
||||||
|
action(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -148,6 +158,10 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserModel resolveUser(AuthenticationFlowContext context, String username) {
|
private UserModel resolveUser(AuthenticationFlowContext context, String username) {
|
||||||
|
if (username == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
UserProvider users = session.users();
|
UserProvider users = session.users();
|
||||||
RealmModel realm = session.getContext().getRealm();
|
RealmModel realm = session.getContext().getRealm();
|
||||||
UserModel user = Optional.ofNullable(users.getUserByEmail(realm, username)).orElseGet(() -> users.getUserByUsername(realm, username));
|
UserModel user = Optional.ofNullable(users.getUserByEmail(realm, username)).orElseGet(() -> users.getUserByUsername(realm, username));
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
import org.keycloak.organization.utils.Organizations;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
|
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
||||||
|
@ -78,18 +79,26 @@ public class OrganizationMembershipMapper extends AbstractOIDCProtocolMapper imp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) {
|
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession session, ClientSessionContext clientSessionCtx) {
|
||||||
OrganizationProvider provider = keycloakSession.getProvider(OrganizationProvider.class);
|
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||||
|
|
||||||
if (!isEnabledAndOrganizationsPresent(provider)) {
|
if (!isEnabledAndOrganizationsPresent(provider)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OrganizationModel organization = Organizations.resolveOrganizationFromScopeParam(session, clientSessionCtx.getScopeString());
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
Stream<OrganizationModel> organizations = provider.getByMember(user).filter(OrganizationModel::isEnabled);
|
Stream<OrganizationModel> organizations = Stream.empty();
|
||||||
|
|
||||||
|
if (organization == null) {
|
||||||
|
organizations = provider.getByMember(user).filter(OrganizationModel::isEnabled);
|
||||||
|
} else if (provider.isMember(organization, user)) {
|
||||||
|
organizations = Stream.of(organization);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Map<String, Object>> claim = new HashMap<>();
|
Map<String, Map<String, Object>> claim = new HashMap<>();
|
||||||
|
|
||||||
organizations.forEach(organization -> claim.put(organization.getAlias(), Map.of()));
|
organizations.forEach(o -> claim.put(o.getAlias(), Map.of()));
|
||||||
|
|
||||||
if (claim.isEmpty()) {
|
if (claim.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -28,6 +28,8 @@ import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.keycloak.TokenVerifier;
|
import org.keycloak.TokenVerifier;
|
||||||
|
@ -46,14 +48,19 @@ import org.keycloak.models.OrganizationDomainModel;
|
||||||
import org.keycloak.models.OrganizationModel;
|
import org.keycloak.models.OrganizationModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.utils.StringUtil;
|
import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
public class Organizations {
|
public class Organizations {
|
||||||
|
|
||||||
|
private static final Pattern SCOPE_PATTERN = Pattern.compile("organization:*".replace("*", "(.*)"));
|
||||||
|
|
||||||
public static boolean canManageOrganizationGroup(KeycloakSession session, GroupModel group) {
|
public static boolean canManageOrganizationGroup(KeycloakSession session, GroupModel group) {
|
||||||
if (!Type.ORGANIZATION.equals(group.getType())) {
|
if (!Type.ORGANIZATION.equals(group.getType())) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -69,7 +76,7 @@ public class Organizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IdentityProviderModel> resolveHomeBroker(KeycloakSession session, UserModel user) {
|
public static List<IdentityProviderModel> resolveHomeBroker(KeycloakSession session, UserModel user) {
|
||||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
OrganizationProvider provider = getProvider(session);
|
||||||
RealmModel realm = session.getContext().getRealm();
|
RealmModel realm = session.getContext().getRealm();
|
||||||
List<OrganizationModel> organizations = Optional.ofNullable(user).stream().flatMap(provider::getByMember)
|
List<OrganizationModel> organizations = Optional.ofNullable(user).stream().flatMap(provider::getByMember)
|
||||||
.filter(OrganizationModel::isEnabled)
|
.filter(OrganizationModel::isEnabled)
|
||||||
|
@ -117,7 +124,7 @@ public class Organizations {
|
||||||
OrganizationModel current = resolveOrganization(session);
|
OrganizationModel current = resolveOrganization(session);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
OrganizationProvider provider = getProvider(session);
|
||||||
|
|
||||||
session.setAttribute(OrganizationModel.class.getName(), provider.getById(group.getName()));
|
session.setAttribute(OrganizationModel.class.getName(), provider.getById(group.getName()));
|
||||||
|
|
||||||
|
@ -136,6 +143,16 @@ public class Organizations {
|
||||||
return orgProvider != null && orgProvider.isEnabled() && orgProvider.count() != 0;
|
return orgProvider != null && orgProvider.isEnabled() && orgProvider.count() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isEnabledAndOrganizationsPresent(KeycloakSession session) {
|
||||||
|
if (!Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrganizationProvider provider = getProvider(session);
|
||||||
|
|
||||||
|
return isEnabledAndOrganizationsPresent(provider);
|
||||||
|
}
|
||||||
|
|
||||||
public static void checkEnabled(OrganizationProvider provider) {
|
public static void checkEnabled(OrganizationProvider provider) {
|
||||||
if (provider == null || !provider.isEnabled()) {
|
if (provider == null || !provider.isEnabled()) {
|
||||||
throw ErrorResponse.error("Organizations not enabled for this realm.", Response.Status.NOT_FOUND);
|
throw ErrorResponse.error("Organizations not enabled for this realm.", Response.Status.NOT_FOUND);
|
||||||
|
@ -236,14 +253,23 @@ public class Organizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OrganizationModel resolveOrganization(KeycloakSession session, UserModel user, String domain) {
|
public static OrganizationModel resolveOrganization(KeycloakSession session, UserModel user, String domain) {
|
||||||
|
OrganizationProvider provider = getProvider(session);
|
||||||
|
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||||
|
|
||||||
|
if (authSession != null) {
|
||||||
|
Optional<OrganizationModel> organization = ofNullable(authSession.getAuthNote(OrganizationModel.ORGANIZATION_ATTRIBUTE)).map(provider::getById);
|
||||||
|
|
||||||
|
if (organization.isPresent()) {
|
||||||
|
return organization.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Optional<OrganizationModel> organization = Optional.ofNullable((OrganizationModel) session.getAttribute(OrganizationModel.class.getName()));
|
Optional<OrganizationModel> organization = Optional.ofNullable((OrganizationModel) session.getAttribute(OrganizationModel.class.getName()));
|
||||||
|
|
||||||
if (organization.isPresent()) {
|
if (organization.isPresent()) {
|
||||||
return organization.get();
|
return organization.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
|
||||||
|
|
||||||
organization = ofNullable(user).stream().flatMap(provider::getByMember)
|
organization = ofNullable(user).stream().flatMap(provider::getByMember)
|
||||||
.filter(o -> o.isEnabled() && provider.isManagedMember(o, user))
|
.filter(o -> o.isEnabled() && provider.isManagedMember(o, user))
|
||||||
.findAny();
|
.findAny();
|
||||||
|
@ -260,4 +286,32 @@ public class Organizations {
|
||||||
.map(provider::getByDomainName)
|
.map(provider::getByDomainName)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static OrganizationModel resolveOrganizationFromScopeParam(KeycloakSession session, String scopeParam) {
|
||||||
|
return TokenManager.parseScopeParameter(scopeParam)
|
||||||
|
.map((s) -> resolveOrganizationFromScope(session, s))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OrganizationModel resolveOrganizationFromScope(KeycloakSession session, String scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = SCOPE_PATTERN.matcher(scope);
|
||||||
|
|
||||||
|
if (matcher.matches()) {
|
||||||
|
return Optional.ofNullable(getProvider(session).getByAlias(matcher.group(1)))
|
||||||
|
.filter(OrganizationModel::isEnabled)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OrganizationProvider getProvider(KeycloakSession session) {
|
||||||
|
return session.getProvider(OrganizationProvider.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ public abstract class AuthorizationEndpointBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
|
|
||||||
if (processor.nextRequiredAction() != null) {
|
if (processor.nextRequiredAction() != null) {
|
||||||
Response response = protocol.sendError(authSession, Error.PASSIVE_INTERACTION_REQUIRED);
|
Response response = protocol.sendError(authSession, Error.PASSIVE_INTERACTION_REQUIRED);
|
||||||
|
|
|
@ -415,7 +415,7 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
|
||||||
|
|
||||||
event.session(targetUserSession);
|
event.session(targetUserSession);
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);
|
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);
|
||||||
|
|
||||||
updateUserSessionFromClientAuth(targetUserSession);
|
updateUserSessionFromClientAuth(targetUserSession);
|
||||||
|
@ -480,7 +480,7 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
|
||||||
|
|
||||||
event.session(targetUserSession);
|
event.session(targetUserSession);
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(this.session, targetUserSession,
|
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(this.session, targetUserSession,
|
||||||
authSession);
|
authSession);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,9 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.ClientSessionContext;
|
import org.keycloak.models.ClientSessionContext;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.ClientScopeDecorator;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -59,6 +61,7 @@ import org.keycloak.models.light.LightweightUserAdapter;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.SessionExpirationUtils;
|
import org.keycloak.models.utils.SessionExpirationUtils;
|
||||||
import org.keycloak.models.utils.RoleUtils;
|
import org.keycloak.models.utils.RoleUtils;
|
||||||
|
import org.keycloak.organization.utils.Organizations;
|
||||||
import org.keycloak.protocol.ProtocolMapper;
|
import org.keycloak.protocol.ProtocolMapper;
|
||||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||||
import org.keycloak.protocol.oidc.mappers.TokenIntrospectionTokenMapper;
|
import org.keycloak.protocol.oidc.mappers.TokenIntrospectionTokenMapper;
|
||||||
|
@ -573,23 +576,25 @@ public class TokenManager {
|
||||||
ClientModel client = authSession.getClient();
|
ClientModel client = authSession.getClient();
|
||||||
|
|
||||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||||
if (clientSession != null && !AuthenticationManager.isClientSessionValid(userSession.getRealm(), client, userSession, clientSession)) {
|
RealmModel realm = userSession.getRealm();
|
||||||
|
if (clientSession != null && !AuthenticationManager.isClientSessionValid(realm, client, userSession, clientSession)) {
|
||||||
// session exists but not active so re-start it
|
// session exists but not active so re-start it
|
||||||
clientSession.restartClientSession();
|
clientSession.restartClientSession();
|
||||||
} else if (clientSession == null) {
|
} else if (clientSession == null) {
|
||||||
clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession);
|
clientSession = session.sessions().createClientSession(realm, client, userSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientSession.setRedirectUri(authSession.getRedirectUri());
|
clientSession.setRedirectUri(authSession.getRedirectUri());
|
||||||
clientSession.setProtocol(authSession.getProtocol());
|
clientSession.setProtocol(authSession.getProtocol());
|
||||||
|
|
||||||
Set<String> clientScopeIds;
|
Set<ClientScopeModel> clientScopes;
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
||||||
clientScopeIds = AuthorizationContextUtil.getClientScopesStreamFromAuthorizationRequestContextWithClient(session, authSession.getClientNote(OAuth2Constants.SCOPE))
|
clientScopes = AuthorizationContextUtil.getClientScopesStreamFromAuthorizationRequestContextWithClient(session, authSession.getClientNote(OAuth2Constants.SCOPE))
|
||||||
.map(ClientScopeModel::getId)
|
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
} else {
|
} else {
|
||||||
clientScopeIds = authSession.getClientScopes();
|
clientScopes = authSession.getClientScopes().stream()
|
||||||
|
.map(id -> KeycloakModelUtils.findClientScopeById(realm, client, id))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> transferredNotes = authSession.getClientNotes();
|
Map<String, String> transferredNotes = authSession.getClientNotes();
|
||||||
|
@ -605,10 +610,9 @@ public class TokenManager {
|
||||||
clientSession.setNote(Constants.LEVEL_OF_AUTHENTICATION, String.valueOf(new AcrStore(session, authSession).getLevelOfAuthenticationFromCurrentAuthentication()));
|
clientSession.setNote(Constants.LEVEL_OF_AUTHENTICATION, String.valueOf(new AcrStore(session, authSession).getLevelOfAuthenticationFromCurrentAuthentication()));
|
||||||
clientSession.setTimestamp(userSession.getLastSessionRefresh());
|
clientSession.setTimestamp(userSession.getLastSessionRefresh());
|
||||||
// Remove authentication session now (just current tab, not whole "rootAuthenticationSession" in case we have more browser tabs with "authentications in progress")
|
// Remove authentication session now (just current tab, not whole "rootAuthenticationSession" in case we have more browser tabs with "authentications in progress")
|
||||||
new AuthenticationSessionManager(session).updateAuthenticationSessionAfterSuccessfulAuthentication(userSession.getRealm(), authSession);
|
new AuthenticationSessionManager(session).updateAuthenticationSessionAfterSuccessfulAuthentication(realm, authSession);
|
||||||
|
|
||||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopeIds(clientSession, clientScopeIds, session);
|
return DefaultClientSessionContext.fromClientSessionAndClientScopes(clientSession, clientScopes, session);
|
||||||
return clientSessionCtx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -660,7 +664,7 @@ public class TokenManager {
|
||||||
|
|
||||||
|
|
||||||
/** Return client itself + all default client scopes of client + optional client scopes requested by scope parameter **/
|
/** Return client itself + all default client scopes of client + optional client scopes requested by scope parameter **/
|
||||||
public static Stream<ClientScopeModel> getRequestedClientScopes(String scopeParam, ClientModel client) {
|
public static Stream<ClientScopeModel> getRequestedClientScopes(KeycloakSession session, String scopeParam, ClientModel client) {
|
||||||
// Add all default client scopes automatically and client itself
|
// Add all default client scopes automatically and client itself
|
||||||
Stream<ClientScopeModel> clientScopes = Stream.concat(
|
Stream<ClientScopeModel> clientScopes = Stream.concat(
|
||||||
client.getClientScopes(true).values().stream(),
|
client.getClientScopes(true).values().stream(),
|
||||||
|
@ -670,9 +674,32 @@ public class TokenManager {
|
||||||
return clientScopes;
|
return clientScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean orgEnabled = Organizations.isEnabledAndOrganizationsPresent(session);
|
||||||
Map<String, ClientScopeModel> allOptionalScopes = client.getClientScopes(false);
|
Map<String, ClientScopeModel> allOptionalScopes = client.getClientScopes(false);
|
||||||
|
|
||||||
// Add optional client scopes requested by scope parameter
|
// Add optional client scopes requested by scope parameter
|
||||||
return Stream.concat(parseScopeParameter(scopeParam).map(allOptionalScopes::get).filter(Objects::nonNull),
|
return Stream.concat(parseScopeParameter(scopeParam)
|
||||||
|
.map(name -> {
|
||||||
|
ClientScopeModel scope = allOptionalScopes.get(name);
|
||||||
|
|
||||||
|
if (scope != null) {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orgEnabled) {
|
||||||
|
OrganizationModel organization = Organizations.resolveOrganizationFromScope(session, name);
|
||||||
|
|
||||||
|
if (organization != null) {
|
||||||
|
ClientScopeModel orgScope = allOptionalScopes.get(OIDCLoginProtocolFactory.ORGANIZATION);
|
||||||
|
return Optional.ofNullable(orgScope)
|
||||||
|
.map((s) -> new ClientScopeDecorator(s, name))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull),
|
||||||
clientScopes).distinct();
|
clientScopes).distinct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,59 +711,61 @@ public class TokenManager {
|
||||||
* @param client
|
* @param client
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static boolean isValidScope(String scopes, AuthorizationRequestContext authorizationRequestContext, ClientModel client) {
|
public static boolean isValidScope(KeycloakSession session, String scopes, AuthorizationRequestContext authorizationRequestContext, ClientModel client) {
|
||||||
if (scopes == null) {
|
if (scopes == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Collection<String> requestedScopes = TokenManager.parseScopeParameter(scopes).collect(Collectors.toSet());
|
|
||||||
Set<String> rarScopes = authorizationRequestContext.getAuthorizationDetailEntries()
|
Collection<String> rawScopes = TokenManager.parseScopeParameter(scopes).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
if (TokenUtil.isOIDCRequest(scopes)) {
|
||||||
|
rawScopes.remove(OAuth2Constants.SCOPE_OPENID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawScopes.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> clientScopes;
|
||||||
|
|
||||||
|
if (authorizationRequestContext == null) {
|
||||||
|
// only true when dynamic scopes feature is enabled
|
||||||
|
clientScopes = getRequestedClientScopes(session, scopes, client)
|
||||||
|
.filter(((Predicate<ClientScopeModel>) ClientModel.class::isInstance).negate())
|
||||||
|
.map(ClientScopeModel::getName)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
} else {
|
||||||
|
List<AuthorizationDetails> details = Optional.ofNullable(authorizationRequestContext.getAuthorizationDetailEntries()).orElse(List.of());
|
||||||
|
|
||||||
|
clientScopes = details
|
||||||
.stream()
|
.stream()
|
||||||
.map(AuthorizationDetails::getAuthorizationDetails)
|
.map(AuthorizationDetails::getAuthorizationDetails)
|
||||||
.map(AuthorizationDetailsJSONRepresentation::getScopeNameFromCustomData)
|
.map(AuthorizationDetailsJSONRepresentation::getScopeNameFromCustomData)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
if (TokenUtil.isOIDCRequest(scopes)) {
|
|
||||||
requestedScopes.remove(OAuth2Constants.SCOPE_OPENID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((authorizationRequestContext.getAuthorizationDetailEntries() == null || authorizationRequestContext.getAuthorizationDetailEntries().isEmpty()) && requestedScopes.size()>0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.tracef("Rar scopes to validate requested scopes against: %1s", String.join(" ", rarScopes));
|
logger.tracef("Scopes to validate requested scopes against: %1s", String.join(" ", clientScopes));
|
||||||
logger.tracef("Requested scopes: %1s", String.join(" ", requestedScopes));
|
logger.tracef("Requested scopes: %1s", String.join(" ", rawScopes));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String requestedScope : requestedScopes) {
|
if (clientScopes.isEmpty()) {
|
||||||
// We keep the check to the getDynamicClientScope for the OpenshiftSAClientAdapter
|
|
||||||
if (!rarScopes.contains(requestedScope) && client.getDynamicClientScope(requestedScope) == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isValidScope(String scopes, ClientModel client) {
|
|
||||||
if (scopes == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> clientScopes = getRequestedClientScopes(scopes, client)
|
|
||||||
.filter(((Predicate<ClientScopeModel>) ClientModel.class::isInstance).negate())
|
|
||||||
.map(ClientScopeModel::getName)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
Collection<String> requestedScopes = TokenManager.parseScopeParameter(scopes).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
if (TokenUtil.isOIDCRequest(scopes)) {
|
|
||||||
requestedScopes.remove(OAuth2Constants.SCOPE_OPENID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!requestedScopes.isEmpty() && clientScopes.isEmpty()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String requestedScope : requestedScopes) {
|
var orgEnabled = Organizations.isEnabledAndOrganizationsPresent(session);
|
||||||
|
|
||||||
|
for (String requestedScope : rawScopes) {
|
||||||
|
if (orgEnabled) {
|
||||||
|
OrganizationModel organization = Organizations.resolveOrganizationFromScope(session, requestedScope);
|
||||||
|
|
||||||
|
if (organization != null) {
|
||||||
|
// propagate the organization to authenticators to provide a hint about the organization the client wants to authenticate
|
||||||
|
session.setAttribute(OrganizationModel.class.getName(), organization);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we also check dynamic scopes in case the client is from a provider that dynamically provides scopes to their clients
|
// we also check dynamic scopes in case the client is from a provider that dynamically provides scopes to their clients
|
||||||
if (!clientScopes.contains(requestedScope) && client.getDynamicClientScope(requestedScope) == null) {
|
if (!clientScopes.contains(requestedScope) && client.getDynamicClientScope(requestedScope) == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -746,6 +775,10 @@ public class TokenManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isValidScope(KeycloakSession session, String scopes, ClientModel client) {
|
||||||
|
return isValidScope(session, scopes, null, client);
|
||||||
|
}
|
||||||
|
|
||||||
public static Stream<String> parseScopeParameter(String scopeParam) {
|
public static Stream<String> parseScopeParameter(String scopeParam) {
|
||||||
return Arrays.stream(scopeParam.split(" ")).distinct();
|
return Arrays.stream(scopeParam.split(" ")).distinct();
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,9 +226,9 @@ public class AuthorizationEndpointChecker {
|
||||||
public void checkValidScope() throws AuthorizationCheckException {
|
public void checkValidScope() throws AuthorizationCheckException {
|
||||||
boolean validScopes;
|
boolean validScopes;
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
||||||
validScopes = TokenManager.isValidScope(request.getScope(), request.getAuthorizationRequestContext(), client);
|
validScopes = TokenManager.isValidScope(session, request.getScope(), request.getAuthorizationRequestContext(), client);
|
||||||
} else {
|
} else {
|
||||||
validScopes = TokenManager.isValidScope(request.getScope(), client);
|
validScopes = TokenManager.isValidScope(session, request.getScope(), client);
|
||||||
}
|
}
|
||||||
if (!validScopes) {
|
if (!validScopes) {
|
||||||
ServicesLogger.LOGGER.invalidParameter(OIDCLoginProtocol.SCOPE_PARAM);
|
ServicesLogger.LOGGER.invalidParameter(OIDCLoginProtocol.SCOPE_PARAM);
|
||||||
|
|
|
@ -187,7 +187,7 @@ public class AuthorizationCodeGrantType extends OAuth2GrantTypeBase {
|
||||||
// Compute client scopes again from scope parameter. Check if user still has them granted
|
// Compute client scopes again from scope parameter. Check if user still has them granted
|
||||||
// (but in code-to-token request, it could just theoretically happen that they are not available)
|
// (but in code-to-token request, it could just theoretically happen that they are not available)
|
||||||
String scopeParam = codeData.getScope();
|
String scopeParam = codeData.getScope();
|
||||||
Supplier<Stream<ClientScopeModel>> clientScopesSupplier = () -> TokenManager.getRequestedClientScopes(scopeParam, client);
|
Supplier<Stream<ClientScopeModel>> clientScopesSupplier = () -> TokenManager.getRequestedClientScopes(session, scopeParam, client);
|
||||||
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopesSupplier.get())) {
|
if (!TokenManager.verifyConsentStillAvailable(session, user, client, clientScopesSupplier.get())) {
|
||||||
String errorMessage = "Client no longer has requested consent from user";
|
String errorMessage = "Client no longer has requested consent from user";
|
||||||
event.detail(Details.REASON, errorMessage);
|
event.detail(Details.REASON, errorMessage);
|
||||||
|
|
|
@ -120,7 +120,7 @@ public class ClientCredentialsGrantType extends OAuth2GrantTypeBase {
|
||||||
clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, sessionPersistenceState);
|
clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, sessionPersistenceState);
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
||||||
|
|
||||||
// Notes about client details
|
// Notes about client details
|
||||||
|
|
|
@ -206,9 +206,9 @@ public abstract class OAuth2GrantTypeBase implements OAuth2GrantType {
|
||||||
boolean validScopes;
|
boolean validScopes;
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
||||||
AuthorizationRequestContext authorizationRequestContext = AuthorizationContextUtil.getAuthorizationRequestContextFromScopes(session, scope);
|
AuthorizationRequestContext authorizationRequestContext = AuthorizationContextUtil.getAuthorizationRequestContextFromScopes(session, scope);
|
||||||
validScopes = TokenManager.isValidScope(scope, authorizationRequestContext, client);
|
validScopes = TokenManager.isValidScope(session, scope, authorizationRequestContext, client);
|
||||||
} else {
|
} else {
|
||||||
validScopes = TokenManager.isValidScope(scope, client);
|
validScopes = TokenManager.isValidScope(session, scope, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validScopes) {
|
if (!validScopes) {
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class ResourceOwnerPasswordCredentialsGrantType extends OAuth2GrantTypeBa
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
|
|
||||||
ClientSessionContext clientSessionCtx = processor.attachSession();
|
ClientSessionContext clientSessionCtx = processor.attachSession();
|
||||||
UserSessionModel userSession = processor.getUserSession();
|
UserSessionModel userSession = processor.getUserSession();
|
||||||
|
|
|
@ -188,7 +188,7 @@ public class CibaGrantType extends OAuth2GrantTypeBase {
|
||||||
|
|
||||||
if (!TokenManager
|
if (!TokenManager
|
||||||
.verifyConsentStillAvailable(session,
|
.verifyConsentStillAvailable(session,
|
||||||
user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
|
user, client, TokenManager.getRequestedClientScopes(session, scopeParam, client))) {
|
||||||
String errorMessage = "Client no longer has requested consent from user";
|
String errorMessage = "Client no longer has requested consent from user";
|
||||||
event.detail(Details.REASON, errorMessage);
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
@ -248,7 +248,7 @@ public class CibaGrantType extends OAuth2GrantTypeBase {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, errorMessage, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
|
|
||||||
ClientSessionContext context = AuthenticationProcessor
|
ClientSessionContext context = AuthenticationProcessor
|
||||||
.attachSession(authSession, null, session, realm, session.getContext().getConnection(), event);
|
.attachSession(authSession, null, session, realm, session.getContext().getConnection(), event);
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "missing parameter : scope",
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "missing parameter : scope",
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
if (!TokenManager.isValidScope(scope, client)) {
|
if (!TokenManager.isValidScope(session, scope, client)) {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Invalid scopes: " + scope,
|
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Invalid scopes: " + scope,
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,7 +336,7 @@ public class DeviceGrantType extends OAuth2GrantTypeBase {
|
||||||
// Compute client scopes again from scope parameter. Check if user still has them granted
|
// Compute client scopes again from scope parameter. Check if user still has them granted
|
||||||
// (but in device_code-to-token request, it could just theoretically happen that they are not available)
|
// (but in device_code-to-token request, it could just theoretically happen that they are not available)
|
||||||
String scopeParam = deviceCodeModel.getScope();
|
String scopeParam = deviceCodeModel.getScope();
|
||||||
if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
|
if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(session, scopeParam, client))) {
|
||||||
String errorMessage = "Client no longer has requested consent from user";
|
String errorMessage = "Client no longer has requested consent from user";
|
||||||
event.detail(Details.REASON, errorMessage);
|
event.detail(Details.REASON, errorMessage);
|
||||||
event.error(Errors.NOT_ALLOWED);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
|
|
@ -1210,14 +1210,14 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void setClientScopesInSession(AuthenticationSessionModel authSession) {
|
public static void setClientScopesInSession(KeycloakSession session, AuthenticationSessionModel authSession) {
|
||||||
ClientModel client = authSession.getClient();
|
ClientModel client = authSession.getClient();
|
||||||
UserModel user = authSession.getAuthenticatedUser();
|
UserModel user = authSession.getAuthenticatedUser();
|
||||||
|
|
||||||
// todo scope param protocol independent
|
// todo scope param protocol independent
|
||||||
String scopeParam = authSession.getClientNote(OAuth2Constants.SCOPE);
|
String scopeParam = authSession.getClientNote(OAuth2Constants.SCOPE);
|
||||||
|
|
||||||
Set<String> requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, client)
|
Set<String> requestedClientScopes = TokenManager.getRequestedClientScopes(session, scopeParam, client)
|
||||||
.map(ClientScopeModel::getId).collect(Collectors.toSet());
|
.map(ClientScopeModel::getId).collect(Collectors.toSet());
|
||||||
|
|
||||||
authSession.setClientScopes(requestedClientScopes);
|
authSession.setClientScopes(requestedClientScopes);
|
||||||
|
|
|
@ -886,7 +886,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
logger.debugf("Performing local authentication for user [%s].", federatedUser);
|
logger.debugf("Performing local authentication for user [%s].", federatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
|
|
||||||
String nextRequiredAction = AuthenticationManager.nextRequiredAction(session, authSession, request, event);
|
String nextRequiredAction = AuthenticationManager.nextRequiredAction(session, authSession, request, event);
|
||||||
if (nextRequiredAction != null) {
|
if (nextRequiredAction != null) {
|
||||||
|
@ -999,7 +999,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
|
|
||||||
context.getIdp().authenticationFinished(authSession, context);
|
context.getIdp().authenticationFinished(authSession, context);
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
||||||
|
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
|
|
|
@ -109,7 +109,7 @@ public class ClientScopeEvaluateResource {
|
||||||
throw new NotFoundException("Role Container not found");
|
throw new NotFoundException("Role Container not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ClientScopeEvaluateScopeMappingsResource(roleContainer, auth, client, scopeParam);
|
return new ClientScopeEvaluateScopeMappingsResource(session, roleContainer, auth, client, scopeParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ public class ClientScopeEvaluateResource {
|
||||||
public Stream<ProtocolMapperEvaluationRepresentation> getGrantedProtocolMappers(@QueryParam("scope") String scopeParam) {
|
public Stream<ProtocolMapperEvaluationRepresentation> getGrantedProtocolMappers(@QueryParam("scope") String scopeParam) {
|
||||||
auth.clients().requireView(client);
|
auth.clients().requireView(client);
|
||||||
|
|
||||||
return TokenManager.getRequestedClientScopes(scopeParam, client)
|
return TokenManager.getRequestedClientScopes(session, scopeParam, client)
|
||||||
.flatMap(mapperContainer -> mapperContainer.getProtocolMappersStream()
|
.flatMap(mapperContainer -> mapperContainer.getProtocolMappersStream()
|
||||||
.filter(current -> isEnabled(session, current) && Objects.equals(current.getProtocol(), client.getProtocol()))
|
.filter(current -> isEnabled(session, current) && Objects.equals(current.getProtocol(), client.getProtocol()))
|
||||||
.map(current -> toProtocolMapperEvaluationRepresentation(current, mapperContainer)));
|
.map(current -> toProtocolMapperEvaluationRepresentation(current, mapperContainer)));
|
||||||
|
@ -252,7 +252,7 @@ public class ClientScopeEvaluateResource {
|
||||||
UserSessionModel userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(),
|
UserSessionModel userSession = new UserSessionManager(session).createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(),
|
||||||
clientConnection.getRemoteAddr(), "example-auth", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
clientConnection.getRemoteAddr(), "example-auth", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT);
|
||||||
|
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
||||||
|
|
||||||
return function.apply(userSession, clientSessionCtx);
|
return function.apply(userSession, clientSessionCtx);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||||
import org.jboss.resteasy.reactive.NoCache;
|
import org.jboss.resteasy.reactive.NoCache;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
@ -47,13 +48,15 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluato
|
||||||
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
|
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
|
||||||
public class ClientScopeEvaluateScopeMappingsResource {
|
public class ClientScopeEvaluateScopeMappingsResource {
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
private final RoleContainerModel roleContainer;
|
private final RoleContainerModel roleContainer;
|
||||||
private final AdminPermissionEvaluator auth;
|
private final AdminPermissionEvaluator auth;
|
||||||
private final ClientModel client;
|
private final ClientModel client;
|
||||||
private final String scopeParam;
|
private final String scopeParam;
|
||||||
|
|
||||||
public ClientScopeEvaluateScopeMappingsResource(RoleContainerModel roleContainer, AdminPermissionEvaluator auth, ClientModel client,
|
public ClientScopeEvaluateScopeMappingsResource(KeycloakSession session, RoleContainerModel roleContainer, AdminPermissionEvaluator auth, ClientModel client,
|
||||||
String scopeParam) {
|
String scopeParam) {
|
||||||
|
this.session = session;
|
||||||
this.roleContainer = roleContainer;
|
this.roleContainer = roleContainer;
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
@ -77,7 +80,7 @@ public class ClientScopeEvaluateScopeMappingsResource {
|
||||||
@Operation( summary = "Get effective scope mapping of all roles of particular role container, which this client is defacto allowed to have in the accessToken issued for him.",
|
@Operation( summary = "Get effective scope mapping of all roles of particular role container, which this client is defacto allowed to have in the accessToken issued for him.",
|
||||||
description = "This contains scope mappings, which this client has directly, as well as scope mappings, which are granted to all client scopes, which are linked with this client.")
|
description = "This contains scope mappings, which this client has directly, as well as scope mappings, which are granted to all client scopes, which are linked with this client.")
|
||||||
public Stream<RoleRepresentation> getGrantedScopeMappings() {
|
public Stream<RoleRepresentation> getGrantedScopeMappings() {
|
||||||
return getGrantedRoles().map(ModelToRepresentation::toBriefRepresentation);
|
return getGrantedRoles(session).map(ModelToRepresentation::toBriefRepresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +97,7 @@ public class ClientScopeEvaluateScopeMappingsResource {
|
||||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS)
|
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENTS)
|
||||||
@Operation( summary = "Get roles, which this client doesn't have scope for and can't have them in the accessToken issued for him.", description = "Defacto all the other roles of particular role container, which are not in {@link #getGrantedScopeMappings()}")
|
@Operation( summary = "Get roles, which this client doesn't have scope for and can't have them in the accessToken issued for him.", description = "Defacto all the other roles of particular role container, which are not in {@link #getGrantedScopeMappings()}")
|
||||||
public Stream<RoleRepresentation> getNotGrantedScopeMappings() {
|
public Stream<RoleRepresentation> getNotGrantedScopeMappings() {
|
||||||
Set<RoleModel> grantedRoles = getGrantedRoles().collect(Collectors.toSet());
|
Set<RoleModel> grantedRoles = getGrantedRoles(session).collect(Collectors.toSet());
|
||||||
|
|
||||||
return roleContainer.getRolesStream()
|
return roleContainer.getRolesStream()
|
||||||
.filter(((Predicate<RoleModel>) grantedRoles::contains).negate())
|
.filter(((Predicate<RoleModel>) grantedRoles::contains).negate())
|
||||||
|
@ -104,12 +107,12 @@ public class ClientScopeEvaluateScopeMappingsResource {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Stream<RoleModel> getGrantedRoles() {
|
private Stream<RoleModel> getGrantedRoles(KeycloakSession session) {
|
||||||
if (client.isFullScopeAllowed()) {
|
if (client.isFullScopeAllowed()) {
|
||||||
return roleContainer.getRolesStream();
|
return roleContainer.getRolesStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(scopeParam, client)
|
Set<ClientScopeModel> clientScopes = TokenManager.getRequestedClientScopes(session, scopeParam, client)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
Predicate<RoleModel> hasClientScope = role ->
|
Predicate<RoleModel> hasClientScope = role ->
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.models.utils.RoleUtils;
|
import org.keycloak.models.utils.RoleUtils;
|
||||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
@ -53,13 +52,13 @@ import org.keycloak.util.TokenUtil;
|
||||||
*/
|
*/
|
||||||
public class DefaultClientSessionContext implements ClientSessionContext {
|
public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
|
|
||||||
private static Logger logger = Logger.getLogger(DefaultClientSessionContext.class);
|
private static final Logger logger = Logger.getLogger(DefaultClientSessionContext.class);
|
||||||
|
|
||||||
private final AuthenticatedClientSessionModel clientSession;
|
private final AuthenticatedClientSessionModel clientSession;
|
||||||
private final Set<String> clientScopeIds;
|
private final Set<ClientScopeModel> requestedScopes;
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
|
||||||
private Set<ClientScopeModel> clientScopes;
|
private Set<ClientScopeModel> allowedClientScopes;
|
||||||
|
|
||||||
//
|
//
|
||||||
private Set<RoleModel> roles;
|
private Set<RoleModel> roles;
|
||||||
|
@ -68,10 +67,12 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
// All roles of user expanded. It doesn't yet take into account permitted clientScopes
|
// All roles of user expanded. It doesn't yet take into account permitted clientScopes
|
||||||
private Set<RoleModel> userRoles;
|
private Set<RoleModel> userRoles;
|
||||||
|
|
||||||
private Map<String, Object> attributes = new HashMap<>();
|
private final Map<String, Object> attributes = new HashMap<>();
|
||||||
|
private Set<String> clientScopeIds;
|
||||||
|
private String scopeString;
|
||||||
|
|
||||||
private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set<String> clientScopeIds, KeycloakSession session) {
|
private DefaultClientSessionContext(AuthenticatedClientSessionModel clientSession, Set<ClientScopeModel> requestedScopes, KeycloakSession session) {
|
||||||
this.clientScopeIds = clientScopeIds;
|
this.requestedScopes = requestedScopes;
|
||||||
this.clientSession = clientSession;
|
this.clientSession = clientSession;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
@ -86,33 +87,21 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
|
|
||||||
|
|
||||||
public static DefaultClientSessionContext fromClientSessionAndScopeParameter(AuthenticatedClientSessionModel clientSession, String scopeParam, KeycloakSession session) {
|
public static DefaultClientSessionContext fromClientSessionAndScopeParameter(AuthenticatedClientSessionModel clientSession, String scopeParam, KeycloakSession session) {
|
||||||
Stream<ClientScopeModel> requestedClientScopes;
|
Stream<ClientScopeModel> requestedScopes;
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
if (Profile.isFeatureEnabled(Profile.Feature.DYNAMIC_SCOPES)) {
|
||||||
session.getContext().setClient(clientSession.getClient());
|
session.getContext().setClient(clientSession.getClient());
|
||||||
requestedClientScopes = AuthorizationContextUtil.getClientScopesStreamFromAuthorizationRequestContextWithClient(session, scopeParam);
|
requestedScopes = AuthorizationContextUtil.getClientScopesStreamFromAuthorizationRequestContextWithClient(session, scopeParam);
|
||||||
} else {
|
} else {
|
||||||
requestedClientScopes = TokenManager.getRequestedClientScopes(scopeParam, clientSession.getClient());
|
requestedScopes = TokenManager.getRequestedClientScopes(session, scopeParam, clientSession.getClient());
|
||||||
}
|
}
|
||||||
return fromClientSessionAndClientScopes(clientSession, requestedClientScopes, session);
|
return new DefaultClientSessionContext(clientSession, requestedScopes.collect(Collectors.toSet()), session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static DefaultClientSessionContext fromClientSessionAndClientScopeIds(AuthenticatedClientSessionModel clientSession, Set<String> clientScopeIds, KeycloakSession session) {
|
public static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession, Set<ClientScopeModel> requestedScopes, KeycloakSession session) {
|
||||||
return new DefaultClientSessionContext(clientSession, clientScopeIds, session);
|
return new DefaultClientSessionContext(clientSession, requestedScopes, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// in order to standardize the way we create this object and with that data, it's better to compute the client scopes internally instead of relying on external sources
|
|
||||||
// i.e: the TokenManager.getRequestedClientScopes was being called in many places to obtain the ClientScopeModel stream.
|
|
||||||
// by changing this method to private, we'll only call it in this class, while also having a single place to put the DYNAMIC_SCOPES feature flag condition
|
|
||||||
private static DefaultClientSessionContext fromClientSessionAndClientScopes(AuthenticatedClientSessionModel clientSession,
|
|
||||||
Stream<ClientScopeModel> clientScopes,
|
|
||||||
KeycloakSession session) {
|
|
||||||
Set<String> clientScopeIds = clientScopes.map(ClientScopeModel::getId).collect(Collectors.toSet());
|
|
||||||
return new DefaultClientSessionContext(clientSession, clientScopeIds, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticatedClientSessionModel getClientSession() {
|
public AuthenticatedClientSessionModel getClientSession() {
|
||||||
return clientSession;
|
return clientSession;
|
||||||
|
@ -121,6 +110,11 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getClientScopeIds() {
|
public Set<String> getClientScopeIds() {
|
||||||
|
if (clientScopeIds == null) {
|
||||||
|
clientScopeIds = requestedScopes.stream()
|
||||||
|
.map(ClientScopeModel::getId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
return clientScopeIds;
|
return clientScopeIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +122,10 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
@Override
|
@Override
|
||||||
public Stream<ClientScopeModel> getClientScopesStream() {
|
public Stream<ClientScopeModel> getClientScopesStream() {
|
||||||
// Load client scopes if not yet present
|
// Load client scopes if not yet present
|
||||||
if (clientScopes == null) {
|
if (allowedClientScopes == null) {
|
||||||
clientScopes = loadClientScopes();
|
allowedClientScopes = requestedScopes.stream().filter(this::isAllowed).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
return clientScopes.stream();
|
return allowedClientScopes.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,7 +160,10 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getScopeString() {
|
public String getScopeString() {
|
||||||
return getScopeString(false);
|
if (scopeString == null) {
|
||||||
|
scopeString = getScopeString(false);
|
||||||
|
}
|
||||||
|
return scopeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -234,27 +231,25 @@ public class DefaultClientSessionContext implements ClientSessionContext {
|
||||||
|
|
||||||
// Loading data
|
// Loading data
|
||||||
|
|
||||||
private Set<ClientScopeModel> loadClientScopes() {
|
private boolean isAllowed(ClientScopeModel clientScope) {
|
||||||
Set<ClientScopeModel> clientScopes = new HashSet<>();
|
|
||||||
for (String scopeId : clientScopeIds) {
|
|
||||||
ClientScopeModel clientScope = KeycloakModelUtils.findClientScopeById(clientSession.getClient().getRealm(), getClientSession().getClient(), scopeId);
|
|
||||||
if (clientScope != null) {
|
|
||||||
if (isClientScopePermittedForUser(clientScope)) {
|
if (isClientScopePermittedForUser(clientScope)) {
|
||||||
clientScopes.add(clientScope);
|
return true;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.tracef("User '%s' not permitted to have client scope '%s'",
|
logger.tracef("User '%s' not permitted to have client scope '%s'",
|
||||||
clientSession.getUserSession().getUser().getUsername(), clientScope.getName());
|
clientSession.getUserSession().getUser().getUsername(), clientScope.getName());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clientScopes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Return true if clientScope can be used by the user.
|
// Return true if clientScope can be used by the user.
|
||||||
private boolean isClientScopePermittedForUser(ClientScopeModel clientScope) {
|
private boolean isClientScopePermittedForUser(ClientScopeModel clientScope) {
|
||||||
|
if (clientScope == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (clientScope instanceof ClientModel) {
|
if (clientScope instanceof ClientModel) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ public class UserSessionUtil {
|
||||||
authSession.setAuthenticatedUser(userSession.getUser());
|
authSession.setAuthenticatedUser(userSession.getUser());
|
||||||
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
|
||||||
AuthenticationManager.setClientScopesInSession(authSession);
|
AuthenticationManager.setClientScopesInSession(session, authSession);
|
||||||
TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
TokenManager.attachAuthenticationSession(session, userSession, authSession);
|
||||||
return userSession;
|
return userSession;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ public class DeclarativeUserProfileProvider implements UserProfileProvider {
|
||||||
String requestedScopesString = authenticationSession.getClientNote(OIDCLoginProtocol.SCOPE_PARAM);
|
String requestedScopesString = authenticationSession.getClientNote(OIDCLoginProtocol.SCOPE_PARAM);
|
||||||
ClientModel client = authenticationSession.getClient();
|
ClientModel client = authenticationSession.getClient();
|
||||||
|
|
||||||
return getRequestedClientScopes(requestedScopesString, client).map((csm) -> csm.getName()).anyMatch(configuredScopes::contains);
|
return getRequestedClientScopes(session, requestedScopesString, client).map((csm) -> csm.getName()).anyMatch(configuredScopes::contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
|
|
@ -117,6 +117,12 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
||||||
return createOrganization(realm, getCleanup(), name, brokerConfigFunction.apply(name).setUpIdentityProvider(), orgDomains);
|
return createOrganization(realm, getCleanup(), name, brokerConfigFunction.apply(name).setUpIdentityProvider(), orgDomains);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected OrganizationRepresentation createOrganization(String name, Map<String, String> brokerConfig) {
|
||||||
|
IdentityProviderRepresentation broker = brokerConfigFunction.apply(name).setUpIdentityProvider();
|
||||||
|
broker.getConfig().putAll(brokerConfig);
|
||||||
|
return createOrganization(testRealm(), getCleanup(), name, broker, name + ".org");
|
||||||
|
}
|
||||||
|
|
||||||
protected OrganizationRepresentation createOrganization(RealmResource testRealm, TestCleanup testCleanup, String name,
|
protected OrganizationRepresentation createOrganization(RealmResource testRealm, TestCleanup testCleanup, String name,
|
||||||
IdentityProviderRepresentation broker, String... orgDomains) {
|
IdentityProviderRepresentation broker, String... orgDomains) {
|
||||||
OrganizationRepresentation org = createRepresentation(name, orgDomains);
|
OrganizationRepresentation org = createRepresentation(name, orgDomains);
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* 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.authentication;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
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.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.RefreshToken;
|
||||||
|
import org.keycloak.representations.idm.MemberRepresentation;
|
||||||
|
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.Assert;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
import org.keycloak.testsuite.broker.KcOidcBrokerConfiguration;
|
||||||
|
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
|
||||||
|
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
|
||||||
|
@EnableFeature(Feature.ORGANIZATION)
|
||||||
|
public class OrganizationAuthenticationTest extends AbstractOrganizationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticateUnmanagedMember() {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
UserRepresentation member = addMember(organization, "contractor@contractor.org");
|
||||||
|
|
||||||
|
// first try to log in using only the email
|
||||||
|
openIdentityFirstLoginPage(member.getEmail(), false, null, false, false);
|
||||||
|
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
// no idp should be shown because there is only a single idp that is bound to an organization
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||||
|
|
||||||
|
// the member should be able to log in using the credentials
|
||||||
|
loginPage.login(memberPassword);
|
||||||
|
appPage.assertCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTryLoginWithUsernameNotAnEmail() {
|
||||||
|
testRealm().organizations().get(createOrganization().getId());
|
||||||
|
|
||||||
|
openIdentityFirstLoginPage("user", false, null, false, false);
|
||||||
|
|
||||||
|
// check if the login page is shown
|
||||||
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
|
||||||
|
testRealm().organizations().get(createOrganization().getId());
|
||||||
|
|
||||||
|
openIdentityFirstLoginPage("user@noorg.org", false, null, false, false);
|
||||||
|
|
||||||
|
// check if the login page is shown
|
||||||
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthenticateUnmanagedMemberWhenProviderDisabled() throws IOException {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
UserRepresentation member = addMember(organization, "contractor@contractor.org");
|
||||||
|
|
||||||
|
// first try to access login page
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||||
|
|
||||||
|
// disable the organization provider
|
||||||
|
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm())
|
||||||
|
.setOrganizationsEnabled(Boolean.FALSE)
|
||||||
|
.update()) {
|
||||||
|
|
||||||
|
// access the page again, now it should be present username and password fields
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
assertThat("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl(), Matchers.containsString("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
// no idp should be shown because there is only a single idp that is bound to an organization
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||||
|
|
||||||
|
// the member should be able to log in using the credentials
|
||||||
|
loginPage.login(member.getEmail(), memberPassword);
|
||||||
|
appPage.assertCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testOrganizationScopeMapsSpecificOrganization() {
|
||||||
|
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();
|
||||||
|
|
||||||
|
// resolve organization based on the organization scope value
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
oauth.scope("organization:" + orgA.getAlias());
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isSocialButtonPresent(orgA.getAlias() + "-identity-provider"));
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(orgB.getAlias() + "-identity-provider"));
|
||||||
|
|
||||||
|
// identity-first login will respect the organization provided in the scope even though the user email maps to a different organization
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
String orgScope = "organization:" + orgB.getAlias();
|
||||||
|
oauth.scope(orgScope);
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isSocialButtonPresent(orgB.getAlias() + "-identity-provider"));
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(orgA.getAlias() + "-identity-provider"));
|
||||||
|
loginPage.loginUsername(member.getEmail());
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isSocialButtonPresent(orgB.getAlias() + "-identity-provider"));
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(orgA.getAlias() + "-identity-provider"));
|
||||||
|
loginPage.login(memberPassword);
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
OAuthClient.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(orgB.getAlias()), is(true));
|
||||||
|
assertThat(response.getRefreshToken(), notNullValue());
|
||||||
|
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||||
|
assertThat(refreshToken.getScope(), containsString(orgScope));
|
||||||
|
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET);
|
||||||
|
assertThat(response.getScope(), containsString(orgScope));
|
||||||
|
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);
|
||||||
|
assertThat(organizations.size(), is(1));
|
||||||
|
assertThat(organizations.containsKey(orgB.getAlias()), is(true));
|
||||||
|
refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||||
|
assertThat(refreshToken.getScope(), containsString(orgScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidOrganizationScope() throws MalformedURLException {
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
oauth.scope("organization:unknown");
|
||||||
|
oauth.realm(TEST_REALM_NAME);
|
||||||
|
oauth.openLoginForm();
|
||||||
|
MultivaluedHashMap<String, String> queryParams = UriUtils.decodeQueryString(new URL(driver.getCurrentUrl()).getQuery());
|
||||||
|
assertEquals("invalid_scope", queryParams.getFirst("error"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.organization.member;
|
package org.keycloak.testsuite.organization.authentication;
|
||||||
|
|
||||||
import org.keycloak.common.Profile.Feature;
|
import org.keycloak.common.Profile.Feature;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
|
@ -1,107 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.member;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
|
||||||
import org.keycloak.common.Profile.Feature;
|
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
|
||||||
import org.keycloak.testsuite.Assert;
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
|
||||||
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
|
|
||||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
|
||||||
|
|
||||||
@EnableFeature(Feature.ORGANIZATION)
|
|
||||||
public class OrganizationMemberAuthenticationTest extends AbstractOrganizationTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuthenticateUnmanagedMember() {
|
|
||||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
|
||||||
UserRepresentation member = addMember(organization, "contractor@contractor.org");
|
|
||||||
|
|
||||||
// first try to log in using only the email
|
|
||||||
openIdentityFirstLoginPage(member.getEmail(), false, null, false, false);
|
|
||||||
|
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
|
||||||
// no idp should be shown because there is only a single idp that is bound to an organization
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
|
||||||
|
|
||||||
// the member should be able to log in using the credentials
|
|
||||||
loginPage.login(memberPassword);
|
|
||||||
appPage.assertCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTryLoginWithUsernameNotAnEmail() {
|
|
||||||
testRealm().organizations().get(createOrganization().getId());
|
|
||||||
|
|
||||||
openIdentityFirstLoginPage("user", false, null, false, false);
|
|
||||||
|
|
||||||
// check if the login page is shown
|
|
||||||
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
|
|
||||||
testRealm().organizations().get(createOrganization().getId());
|
|
||||||
|
|
||||||
openIdentityFirstLoginPage("user@noorg.org", false, null, false, false);
|
|
||||||
|
|
||||||
// check if the login page is shown
|
|
||||||
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuthenticateUnmanagedMemberWhenProviderDisabled() throws IOException {
|
|
||||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
|
||||||
UserRepresentation member = addMember(organization, "contractor@contractor.org");
|
|
||||||
|
|
||||||
// first try to access login page
|
|
||||||
oauth.clientId("broker-app");
|
|
||||||
loginPage.open(bc.consumerRealmName());
|
|
||||||
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
|
||||||
|
|
||||||
// disable the organization provider
|
|
||||||
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm())
|
|
||||||
.setOrganizationsEnabled(Boolean.FALSE)
|
|
||||||
.update()) {
|
|
||||||
|
|
||||||
// access the page again, now it should be present username and password fields
|
|
||||||
loginPage.open(bc.consumerRealmName());
|
|
||||||
|
|
||||||
waitForPage(driver, "sign in to", true);
|
|
||||||
assertThat("Driver should be on the consumer realm page right now",
|
|
||||||
driver.getCurrentUrl(), Matchers.containsString("/auth/realms/" + bc.consumerRealmName() + "/"));
|
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
|
||||||
// no idp should be shown because there is only a single idp that is bound to an organization
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
|
||||||
|
|
||||||
// the member should be able to log in using the credentials
|
|
||||||
loginPage.login(member.getEmail(), memberPassword);
|
|
||||||
appPage.assertCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue