Improvements to the organization authentication flow
Closes #29416 Closes #29417 Closes #29418 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
2055cf62f2
commit
77b58275ca
12 changed files with 421 additions and 116 deletions
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
|
import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -55,13 +56,13 @@ import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -463,7 +464,7 @@ public class ModelToRepresentation {
|
||||||
rep.setWebAuthnPolicyPasswordlessExtraOrigins(webAuthnPolicy.getExtraOrigins());
|
rep.setWebAuthnPolicyPasswordlessExtraOrigins(webAuthnPolicy.getExtraOrigins());
|
||||||
|
|
||||||
CibaConfig cibaPolicy = realm.getCibaPolicy();
|
CibaConfig cibaPolicy = realm.getCibaPolicy();
|
||||||
Map<String, String> attrMap = Optional.ofNullable(rep.getAttributes()).orElse(new HashMap<>());
|
Map<String, String> attrMap = ofNullable(rep.getAttributes()).orElse(new HashMap<>());
|
||||||
attrMap.put(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE, cibaPolicy.getBackchannelTokenDeliveryMode());
|
attrMap.put(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE, cibaPolicy.getBackchannelTokenDeliveryMode());
|
||||||
attrMap.put(CibaConfig.CIBA_EXPIRES_IN, String.valueOf(cibaPolicy.getExpiresIn()));
|
attrMap.put(CibaConfig.CIBA_EXPIRES_IN, String.valueOf(cibaPolicy.getExpiresIn()));
|
||||||
attrMap.put(CibaConfig.CIBA_INTERVAL, String.valueOf(cibaPolicy.getPoolingInterval()));
|
attrMap.put(CibaConfig.CIBA_INTERVAL, String.valueOf(cibaPolicy.getPoolingInterval()));
|
||||||
|
@ -826,7 +827,7 @@ public class ModelToRepresentation {
|
||||||
ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation();
|
ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation();
|
||||||
rep.setId(model.getId());
|
rep.setId(model.getId());
|
||||||
rep.setProtocol(model.getProtocol());
|
rep.setProtocol(model.getProtocol());
|
||||||
Map<String, String> config = new HashMap<>(model.getConfig());
|
Map<String, String> config = new HashMap<>(ofNullable(model.getConfig()).orElse(Collections.emptyMap()));
|
||||||
rep.setConfig(config);
|
rep.setConfig(config);
|
||||||
rep.setName(model.getName());
|
rep.setName(model.getName());
|
||||||
rep.setProtocolMapper(model.getProtocolMapper());
|
rep.setProtocolMapper(model.getProtocolMapper());
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.organization.admin.resource;
|
package org.keycloak.organization.admin.resource;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import static java.util.Optional.ofNullable;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -78,7 +78,7 @@ public class OrganizationResource {
|
||||||
throw ErrorResponse.error("Organization cannot be null.", Response.Status.BAD_REQUEST);
|
throw ErrorResponse.error("Organization cannot be null.", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> domains = organization.getDomains().stream().map(OrganizationDomainRepresentation::getName).collect(Collectors.toSet());
|
Set<String> domains = ofNullable(organization.getDomains()).orElse(Set.of()).stream().map(OrganizationDomainRepresentation::getName).collect(Collectors.toSet());
|
||||||
OrganizationModel model = provider.create(organization.getName(), domains);
|
OrganizationModel model = provider.create(organization.getName(), domains);
|
||||||
|
|
||||||
toModel(organization, model);
|
toModel(organization, model);
|
||||||
|
@ -198,7 +198,7 @@ public class OrganizationResource {
|
||||||
model.setEnabled(rep.isEnabled());
|
model.setEnabled(rep.isEnabled());
|
||||||
model.setDescription(rep.getDescription());
|
model.setDescription(rep.getDescription());
|
||||||
model.setAttributes(rep.getAttributes());
|
model.setAttributes(rep.getAttributes());
|
||||||
model.setDomains(Optional.ofNullable(rep.getDomains()).orElse(Set.of()).stream()
|
model.setDomains(ofNullable(rep.getDomains()).orElse(Set.of()).stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.map(this::toModel)
|
.map(this::toModel)
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()));
|
||||||
|
|
|
@ -24,6 +24,8 @@ import jakarta.ws.rs.core.MultivaluedMap;
|
||||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
import org.keycloak.authentication.AuthenticationFlowError;
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
import org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator;
|
import org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator;
|
||||||
|
import org.keycloak.forms.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.forms.login.freemarker.model.AuthenticationContextBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
||||||
import org.keycloak.http.HttpRequest;
|
import org.keycloak.http.HttpRequest;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
@ -32,8 +34,13 @@ import org.keycloak.models.KeycloakSession;
|
||||||
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.utils.FormMessage;
|
||||||
import org.keycloak.organization.OrganizationProvider;
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
|
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.services.messages.Messages;
|
||||||
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
|
|
||||||
|
@ -68,8 +75,6 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizationProvider provider = getOrganizationProvider();
|
|
||||||
OrganizationModel organization = null;
|
|
||||||
RealmModel realm = context.getRealm();
|
RealmModel realm = context.getRealm();
|
||||||
UserModel user = session.users().getUserByEmail(realm, username);
|
UserModel user = session.users().getUserByEmail(realm, username);
|
||||||
|
|
||||||
|
@ -80,67 +85,31 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
organization = provider.getByMember(user);
|
IdentityProviderModel broker = resolveBroker(user);
|
||||||
|
|
||||||
if (organization != null) {
|
if (broker == null) {
|
||||||
if (provider.isManagedMember(organization, user)) {
|
// not a managed member, continue with the regular flow
|
||||||
// user is a managed member, try to resolve the origin broker and redirect automatically
|
context.attempted();
|
||||||
List<IdentityProviderModel> organizationBrokers = organization.getIdentityProviders().toList();
|
} else {
|
||||||
List<IdentityProviderModel> originBrokers = session.users().getFederatedIdentitiesStream(realm, user)
|
// user is a managed member and associated with a broker, redirect automatically
|
||||||
.map(f -> {
|
redirect(context, broker.getAlias(), user.getEmail());
|
||||||
IdentityProviderModel broker = realm.getIdentityProviderByAlias(f.getIdentityProvider());
|
|
||||||
|
|
||||||
if (!organizationBrokers.contains(broker)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
FederatedIdentityModel identity = session.users().getFederatedIdentity(realm, user, broker.getAlias());
|
|
||||||
|
|
||||||
if (identity != null) {
|
|
||||||
return broker;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}).filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
|
|
||||||
if (originBrokers.size() == 1) {
|
|
||||||
redirect(context, originBrokers.get(0).getAlias());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.attempted();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization == null) {
|
OrganizationProvider provider = getOrganizationProvider();
|
||||||
organization = provider.getByDomainName(emailDomain);
|
OrganizationModel organization = provider.getByDomainName(emailDomain);
|
||||||
}
|
|
||||||
|
|
||||||
if (organization == null) {
|
if (organization == null || !organization.isEnabled()) {
|
||||||
// request does not map to any organization, go to the next step/sub-flow
|
// request does not map to any organization, go to the next step/sub-flow
|
||||||
context.attempted();
|
context.attempted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IdentityProviderModel> domainBrokers = organization.getIdentityProviders().toList();
|
List<IdentityProviderModel> brokers = organization.getIdentityProviders().toList();
|
||||||
|
|
||||||
if (domainBrokers.isEmpty()) {
|
for (IdentityProviderModel broker : brokers) {
|
||||||
// no organization brokers to automatically redirect the user, go to the next step/sub-flow
|
|
||||||
context.attempted();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domainBrokers.size() == 1) {
|
|
||||||
// there is a single broker, redirect the user to authenticate
|
|
||||||
redirect(context, domainBrokers.get(0).getAlias(), username);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (IdentityProviderModel broker : domainBrokers) {
|
|
||||||
String idpDomain = broker.getConfig().get(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
String idpDomain = broker.getConfig().get(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
|
||||||
if (emailDomain.equals(idpDomain)) {
|
if (emailDomain.equals(idpDomain)) {
|
||||||
|
@ -150,31 +119,98 @@ public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the user is authenticating in the scope of the organization, show the identity-first login page and the
|
if (!hasPublicBrokers(brokers)) {
|
||||||
|
// the user does not exist, and there is no broker available for selection, redirect the user to the identity-first login page at the realm
|
||||||
|
challenge(username, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the user does not exist and is authenticating in the scope of the organization, show the identity-first login page and the
|
||||||
// public organization brokers for selection
|
// public organization brokers for selection
|
||||||
context.challenge(context.form()
|
LoginFormsProvider form = context.form()
|
||||||
.setAttributeMapper(attributes -> {
|
.setAttributeMapper(attributes -> {
|
||||||
attributes.computeIfPresent("social",
|
attributes.computeIfPresent("social",
|
||||||
(key, bean) -> new OrganizationAwareIdentityProviderBean((IdentityProviderBean) bean, session, true)
|
(key, bean) -> new OrganizationAwareIdentityProviderBean((IdentityProviderBean) bean, session, true)
|
||||||
);
|
);
|
||||||
|
attributes.computeIfPresent("auth",
|
||||||
|
(key, bean) -> new OrganizationAwareAuthenticationContextBean((AuthenticationContextBean) bean, false)
|
||||||
|
);
|
||||||
|
attributes.computeIfPresent("realm",
|
||||||
|
(key, bean) -> new OrganizationAwareRealmBean(realm)
|
||||||
|
);
|
||||||
return attributes;
|
return attributes;
|
||||||
})
|
});
|
||||||
|
form.addError(new FormMessage("Your email domain matches the " + organization.getName() + " organization but you don't have an account yet."));
|
||||||
|
context.challenge(form
|
||||||
.createLoginUsername());
|
.createLoginUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean hasPublicBrokers(List<IdentityProviderModel> brokers) {
|
||||||
|
return brokers.stream().anyMatch(p -> Boolean.parseBoolean(p.getConfig().getOrDefault(OrganizationModel.BROKER_PUBLIC, Boolean.FALSE.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdentityProviderModel resolveBroker(UserModel user) {
|
||||||
|
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
OrganizationModel organization = provider.getByMember(user);
|
||||||
|
|
||||||
|
if (organization == null || !organization.isEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.isManagedMember(organization, user)) {
|
||||||
|
// user is a managed member, try to resolve the origin broker and redirect automatically
|
||||||
|
List<IdentityProviderModel> organizationBrokers = organization.getIdentityProviders().toList();
|
||||||
|
List<IdentityProviderModel> brokers = session.users().getFederatedIdentitiesStream(realm, user)
|
||||||
|
.map(f -> {
|
||||||
|
IdentityProviderModel broker = realm.getIdentityProviderByAlias(f.getIdentityProvider());
|
||||||
|
|
||||||
|
if (!organizationBrokers.contains(broker)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FederatedIdentityModel identity = session.users().getFederatedIdentity(realm, user, broker.getAlias());
|
||||||
|
|
||||||
|
if (identity != null) {
|
||||||
|
return broker;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}).filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return brokers.size() == 1 ? brokers.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private OrganizationProvider getOrganizationProvider() {
|
private OrganizationProvider getOrganizationProvider() {
|
||||||
return session.getProvider(OrganizationProvider.class);
|
return session.getProvider(OrganizationProvider.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void challenge(AuthenticationFlowContext context){
|
private void challenge(AuthenticationFlowContext context) {
|
||||||
|
challenge(null, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void challenge(String username, AuthenticationFlowContext context){
|
||||||
// the default challenge won't show any broker but just the identity-first login page and the option to try a different authentication mechanism
|
// the default challenge won't show any broker but just the identity-first login page and the option to try a different authentication mechanism
|
||||||
context.challenge(context.form()
|
LoginFormsProvider form = context.form()
|
||||||
.setAttributeMapper(attributes -> {
|
.setAttributeMapper(attributes -> {
|
||||||
// removes identity provider related attributes from forms
|
attributes.computeIfPresent("social",
|
||||||
attributes.remove("social");
|
(key, bean) -> new OrganizationAwareIdentityProviderBean((IdentityProviderBean) bean, session, false, true)
|
||||||
|
);
|
||||||
|
attributes.computeIfPresent("auth",
|
||||||
|
(key, bean) -> new OrganizationAwareAuthenticationContextBean((AuthenticationContextBean) bean, false)
|
||||||
|
);
|
||||||
return attributes;
|
return attributes;
|
||||||
})
|
});
|
||||||
.createLoginUsername());
|
|
||||||
|
if (username != null) {
|
||||||
|
form.addError(new FormMessage(Validation.FIELD_USERNAME, Messages.INVALID_USER));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.challenge(form.createLoginUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getEmailDomain(String email) {
|
private String getEmailDomain(String email) {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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.organization.forms.login.freemarker.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.authentication.AuthenticationSelectionOption;
|
||||||
|
import org.keycloak.forms.login.freemarker.model.AuthenticationContextBean;
|
||||||
|
|
||||||
|
public class OrganizationAwareAuthenticationContextBean extends AuthenticationContextBean {
|
||||||
|
|
||||||
|
private final AuthenticationContextBean delegate;
|
||||||
|
private final boolean showTryAnotherWayLink;
|
||||||
|
|
||||||
|
public OrganizationAwareAuthenticationContextBean(AuthenticationContextBean delegate, boolean showTryAnotherWayLink) {
|
||||||
|
super(null, null);
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.showTryAnotherWayLink = showTryAnotherWayLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuthenticationSelectionOption> getAuthenticationSelections() {
|
||||||
|
return delegate.getAuthenticationSelections();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showTryAnotherWayLink() {
|
||||||
|
if (showTryAnotherWayLink) {
|
||||||
|
return delegate.showTryAnotherWayLink();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showUsername() {
|
||||||
|
return delegate.showUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showResetCredentials() {
|
||||||
|
return delegate.showResetCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttemptedUsername() {
|
||||||
|
return delegate.getAttemptedUsername();
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.organization.forms.login.freemarker.model;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
import org.keycloak.forms.login.freemarker.model.IdentityProviderBean;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
@ -32,8 +33,16 @@ public class OrganizationAwareIdentityProviderBean extends IdentityProviderBean
|
||||||
private final List<IdentityProvider> providers;
|
private final List<IdentityProvider> providers;
|
||||||
|
|
||||||
public OrganizationAwareIdentityProviderBean(IdentityProviderBean delegate, KeycloakSession session, boolean onlyOrganizationBrokers) {
|
public OrganizationAwareIdentityProviderBean(IdentityProviderBean delegate, KeycloakSession session, boolean onlyOrganizationBrokers) {
|
||||||
|
this(delegate, session, onlyOrganizationBrokers, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrganizationAwareIdentityProviderBean(IdentityProviderBean delegate, KeycloakSession session, boolean onlyOrganizationBrokers, boolean onlyRealmBrokers) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
if (onlyOrganizationBrokers) {
|
if (onlyRealmBrokers) {
|
||||||
|
providers = Optional.ofNullable(delegate.getProviders()).orElse(List.of()).stream()
|
||||||
|
.filter(Predicate.not(this::isPublicOrganizationBroker))
|
||||||
|
.toList();
|
||||||
|
} else if (onlyOrganizationBrokers) {
|
||||||
providers = Optional.ofNullable(delegate.getProviders()).orElse(List.of()).stream()
|
providers = Optional.ofNullable(delegate.getProviders()).orElse(List.of()).stream()
|
||||||
.filter(this::isPublicOrganizationBroker)
|
.filter(this::isPublicOrganizationBroker)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.keycloak.organization.forms.login.freemarker.model;
|
||||||
|
|
||||||
|
import org.keycloak.forms.login.freemarker.model.RealmBean;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
public class OrganizationAwareRealmBean extends RealmBean {
|
||||||
|
|
||||||
|
public OrganizationAwareRealmBean(RealmModel realmModel) {
|
||||||
|
super(realmModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRegistrationAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -358,6 +358,7 @@ public class DeclarativeUserProfileProviderFactory implements UserProfileProvide
|
||||||
metadata.addAttribute(OrganizationModel.ORGANIZATION_ATTRIBUTE, -1,
|
metadata.addAttribute(OrganizationModel.ORGANIZATION_ATTRIBUTE, -1,
|
||||||
new AttributeValidatorMetadata(OrganizationMemberValidator.ID),
|
new AttributeValidatorMetadata(OrganizationMemberValidator.ID),
|
||||||
new AttributeValidatorMetadata(ImmutableAttributeValidator.ID))
|
new AttributeValidatorMetadata(ImmutableAttributeValidator.ID))
|
||||||
|
.addReadCondition(c -> USER_API.equals(c.getContext()))
|
||||||
.addWriteCondition(context -> {
|
.addWriteCondition(context -> {
|
||||||
// the attribute can only be managed within the scope of the Organization API
|
// the attribute can only be managed within the scope of the Organization API
|
||||||
// we assume, for now, that if the organization is set as a session attribute, we are operating within the scope if the Organization API
|
// we assume, for now, that if the organization is set as a session attribute, we are operating within the scope if the Organization API
|
||||||
|
|
|
@ -76,7 +76,7 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
|
public void testDefaultAuthenticationIfUserDoesNotExistAndNoOrgMatch() {
|
||||||
testRealm().organizations().get(createOrganization().getId());
|
testRealm().organizations().get(createOrganization().getId());
|
||||||
oauth.clientId("broker-app");
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
|
@ -85,12 +85,171 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
log.debug("Logging in");
|
log.debug("Logging in");
|
||||||
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
loginPage.loginUsername("user@noorg.org");
|
loginPage.loginUsername("user@noorg.org");
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
// check if the login page is shown
|
// check if the login page is shown
|
||||||
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdentityFirstIfUserNotExistsAndEmailMatchOrgDomain() {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
IdentityProviderRepresentation idpRep = organization.identityProviders().getIdentityProviders().get(0);
|
||||||
|
idpRep.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
|
// login with email only
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
log.debug("Logging in");
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
loginPage.loginUsername("user@neworg.org");
|
||||||
|
|
||||||
|
// should stay at the identity-first login page
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
|
// registration link shown
|
||||||
|
Assert.assertTrue(loginPage.isRegisterLinkPresent());
|
||||||
|
// no need for password because the user does not exist
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(idpRep.getAlias()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdentityFirstUserNotExistEmailMatchBrokerDomainAndBrokerIsPublic() {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
IdentityProviderRepresentation idpRep = organization.identityProviders().getIdentityProviders().get(0);
|
||||||
|
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
||||||
|
idpRep.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
|
// login with email only
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
log.debug("Logging in");
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isRegisterLinkPresent());
|
||||||
|
loginPage.loginUsername("user@neworg.org");
|
||||||
|
|
||||||
|
// should stay at the identity-first login page
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
Assert.assertEquals("Your email domain matches the neworg organization but you don't have an account yet.", loginPage.getError());
|
||||||
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isSocialButtonPresent(idpRep.getAlias()));
|
||||||
|
|
||||||
|
// no self-registration link because the user should register through the broker
|
||||||
|
Assert.assertFalse(loginPage.isRegisterLinkPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdentityFirstUserNotExistEmailMatchBrokerDomainNoPublicBroker() {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
IdentityProviderRepresentation idpRep = organization.identityProviders().getIdentityProviders().get(0);
|
||||||
|
idpRep.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
|
// login with email only
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
log.debug("Logging in");
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isRegisterLinkPresent());
|
||||||
|
loginPage.loginUsername("user@neworg.org");
|
||||||
|
|
||||||
|
// should stay at the identity-first login page
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
Assert.assertFalse(driver.getPageSource().contains("Your email domain matches the neworg organization but you don't have an account yet."));
|
||||||
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
// self-registration link shown because there is no public broker and user can choose to register
|
||||||
|
Assert.assertTrue(loginPage.isRegisterLinkPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultAuthenticationShowsPublicOrganizationBrokers() {
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
OrganizationRepresentation representation = organization.toRepresentation();
|
||||||
|
representation.addDomain(new OrganizationDomainRepresentation("other.org"));
|
||||||
|
organization.update(representation).close();
|
||||||
|
IdentityProviderRepresentation idp = organization.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||||
|
idp.getConfig().put(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE, "neworg.org");
|
||||||
|
// set a domain to the existing broker
|
||||||
|
testRealm().identityProviders().get(bc.getIDPAlias()).update(idp);
|
||||||
|
|
||||||
|
idp = bc.setUpIdentityProvider();
|
||||||
|
idp.setAlias("second-idp");
|
||||||
|
idp.setInternalId(null);
|
||||||
|
idp.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
idp.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
||||||
|
// create a second broker without a domain set
|
||||||
|
testRealm().identityProviders().create(idp).close();
|
||||||
|
getCleanup().addCleanup(testRealm().identityProviders().get("second-idp")::remove);
|
||||||
|
organization.identityProviders().addIdentityProvider(idp.getAlias()).close();
|
||||||
|
idp = organization.identityProviders().get(idp.getAlias()).toRepresentation();
|
||||||
|
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
log.debug("Logging in");
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(idp.getAlias()));
|
||||||
|
loginPage.loginUsername("external@user.org");
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||||
|
Assert.assertTrue(loginPage.isSocialButtonPresent(idp.getAlias()));
|
||||||
|
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
idp.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.FALSE.toString());
|
||||||
|
testRealm().identityProviders().get(idp.getAlias()).update(idp);
|
||||||
|
driver.navigate().refresh();
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||||
|
Assert.assertFalse(loginPage.isSocialButtonPresent(idp.getAlias()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultAuthenticationWhenUserExistEmailMatchOrgDomain() {
|
||||||
|
realmsResouce().realm(bc.consumerRealmName()).users()
|
||||||
|
.create(UserBuilder.create()
|
||||||
|
.username("user@neworg.org")
|
||||||
|
.email("user@neworg.org")
|
||||||
|
.password(bc.getUserPassword())
|
||||||
|
.enabled(true).build()
|
||||||
|
).close();
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
IdentityProviderRepresentation idpRep = organization.identityProviders().getIdentityProviders().get(0);
|
||||||
|
idpRep.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
|
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
|
// login with email only
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
log.debug("Logging in");
|
||||||
|
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||||
|
loginPage.loginUsername("user@neworg.org");
|
||||||
|
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||||
|
Assert.assertTrue(loginPage.isRegisterLinkPresent());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRealmLevelBrokersAvailableIfEmailDoesNotMatchOrganization() {
|
public void testRealmLevelBrokersAvailableIfEmailDoesNotMatchOrganization() {
|
||||||
testRealm().organizations().get(createOrganization().getId());
|
testRealm().organizations().get(createOrganization().getId());
|
||||||
|
@ -131,12 +290,20 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
).close();
|
).close();
|
||||||
|
|
||||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
OrganizationIdentityProviderResource broker = organization.identityProviders().get(bc.getIDPAlias());
|
||||||
|
IdentityProviderRepresentation brokerRep = broker.toRepresentation();
|
||||||
|
brokerRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
||||||
|
testRealm().identityProviders().get(brokerRep.getAlias()).update(brokerRep);
|
||||||
oauth.clientId("broker-app");
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
// login with email only
|
// login with email only
|
||||||
loginPage.open(bc.consumerRealmName());
|
loginPage.open(bc.consumerRealmName());
|
||||||
log.debug("Logging in");
|
log.debug("Logging in");
|
||||||
loginPage.loginUsername(bc.getUserEmail());
|
loginPage.loginUsername(bc.getUserEmail());
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
loginPage.clickSocial(bc.getIDPAlias());
|
||||||
|
|
||||||
// user automatically redirected to the organization identity provider
|
// user automatically redirected to the organization identity provider
|
||||||
waitForPage(driver, "sign in to", true);
|
waitForPage(driver, "sign in to", true);
|
||||||
|
@ -162,7 +329,57 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReAuthenticateWhenAlreadyMember() {
|
public void testExistingUserUsingOrgDomain() {
|
||||||
|
// create a realm user in the consumer realm
|
||||||
|
realmsResouce().realm(bc.consumerRealmName()).users()
|
||||||
|
.create(UserBuilder.create()
|
||||||
|
.username(bc.getUserLogin())
|
||||||
|
.email(bc.getUserEmail())
|
||||||
|
.password(bc.getUserPassword())
|
||||||
|
.enabled(true).build()
|
||||||
|
).close();
|
||||||
|
|
||||||
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
OrganizationIdentityProviderResource broker = organization.identityProviders().get(bc.getIDPAlias());
|
||||||
|
IdentityProviderRepresentation brokerRep = broker.toRepresentation();
|
||||||
|
brokerRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
||||||
|
testRealm().identityProviders().get(brokerRep.getAlias()).update(brokerRep);
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
|
||||||
|
// login with email only
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
log.debug("Logging in");
|
||||||
|
loginPage.loginUsername(bc.getUserEmail());
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
loginPage.clickSocial(bc.getIDPAlias());
|
||||||
|
|
||||||
|
// user automatically redirected to the organization identity provider
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||||
|
|
||||||
|
// login to the organization identity provider and run the configured first broker login flow
|
||||||
|
loginPage.login(bc.getUserEmail(), bc.getUserPassword());
|
||||||
|
waitForPage(driver, "update account information", false);
|
||||||
|
updateAccountInformationPage.assertCurrent();
|
||||||
|
Assert.assertTrue("We must be on correct realm right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
|
log.debug("Updating info on updateAccount page");
|
||||||
|
updateAccountInformationPage.updateAccountInformation(bc.getUserEmail(), bc.getUserEmail(), "Firstname", "Lastname");
|
||||||
|
|
||||||
|
// account with the same email exists in the realm, execute account linking
|
||||||
|
waitForPage(driver, "account already exists", false);
|
||||||
|
idpConfirmLinkPage.assertCurrent();
|
||||||
|
idpConfirmLinkPage.clickLinkAccount();
|
||||||
|
// confirm the link by authenticating
|
||||||
|
loginPage.login(bc.getUserEmail(), bc.getUserPassword());
|
||||||
|
assertIsMember(bc.getUserEmail(), organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRedirectBrokerWhenManagedMember() {
|
||||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
|
||||||
// add the member for the first time
|
// add the member for the first time
|
||||||
|
@ -281,47 +498,6 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
assertEquals(bc.getIDPAlias(), federatedIdentities.get(0).getIdentityProvider());
|
assertEquals(bc.getIDPAlias(), federatedIdentities.get(0).getIdentityProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIdentityFirstLoginShowsPublicOrganizationBrokers() {
|
|
||||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
|
||||||
OrganizationRepresentation representation = organization.toRepresentation();
|
|
||||||
representation.addDomain(new OrganizationDomainRepresentation("other.org"));
|
|
||||||
organization.update(representation).close();
|
|
||||||
IdentityProviderRepresentation idp = organization.identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
|
||||||
idp.getConfig().put(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE, "neworg.org");
|
|
||||||
// set a domain to the existing broker
|
|
||||||
testRealm().identityProviders().get(bc.getIDPAlias()).update(idp);
|
|
||||||
|
|
||||||
idp = bc.setUpIdentityProvider();
|
|
||||||
idp.setAlias("second-idp");
|
|
||||||
idp.setInternalId(null);
|
|
||||||
idp.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
|
||||||
idp.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
|
||||||
// create a second broker without a domain set
|
|
||||||
testRealm().identityProviders().create(idp).close();
|
|
||||||
getCleanup().addCleanup(testRealm().identityProviders().get("second-idp")::remove);
|
|
||||||
organization.identityProviders().addIdentityProvider(idp.getAlias()).close();
|
|
||||||
idp = organization.identityProviders().get(idp.getAlias()).toRepresentation();
|
|
||||||
|
|
||||||
oauth.clientId("broker-app");
|
|
||||||
loginPage.open(bc.consumerRealmName());
|
|
||||||
log.debug("Logging in");
|
|
||||||
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(idp.getAlias()));
|
|
||||||
loginPage.loginUsername("external@user.org");
|
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
|
||||||
Assert.assertTrue(loginPage.isSocialButtonPresent(idp.getAlias()));
|
|
||||||
|
|
||||||
idp.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.FALSE.toString());
|
|
||||||
testRealm().identityProviders().get(idp.getAlias()).update(idp);
|
|
||||||
driver.navigate().refresh();
|
|
||||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
|
||||||
Assert.assertFalse(loginPage.isSocialButtonPresent(idp.getAlias()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoginUsingBrokerWithoutDomain() {
|
public void testLoginUsingBrokerWithoutDomain() {
|
||||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||||
|
@ -506,6 +682,7 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
|
|
||||||
// make sure the user can select this idp from the organization when authenticating
|
// make sure the user can select this idp from the organization when authenticating
|
||||||
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
||||||
|
idpRep.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
||||||
|
|
||||||
// create a user to the provider realm using a email that does not share the same domain as the org
|
// create a user to the provider realm using a email that does not share the same domain as the org
|
||||||
|
@ -544,6 +721,7 @@ public abstract class AbstractBrokerSelfRegistrationTest extends AbstractOrganiz
|
||||||
|
|
||||||
// make sure the user can select this idp from the organization when authenticating
|
// make sure the user can select this idp from the organization when authenticating
|
||||||
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
idpRep.getConfig().put(OrganizationModel.BROKER_PUBLIC, Boolean.TRUE.toString());
|
||||||
|
idpRep.getConfig().remove(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
|
||||||
|
|
||||||
// create a user to the provider realm using a email that does not share the same domain as the org
|
// create a user to the provider realm using a email that does not share the same domain as the org
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.testsuite.organization.admin;
|
package org.keycloak.testsuite.organization.admin;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.Response.Status;
|
import jakarta.ws.rs.core.Response.Status;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.admin.client.resource.UsersResource;
|
import org.keycloak.admin.client.resource.UsersResource;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
@ -102,6 +105,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
||||||
id = ApiUtil.getCreatedId(response);
|
id = ApiUtil.getCreatedId(response);
|
||||||
}
|
}
|
||||||
IdentityProviderRepresentation broker = brokerConfigFunction.apply(name).setUpIdentityProvider();
|
IdentityProviderRepresentation broker = brokerConfigFunction.apply(name).setUpIdentityProvider();
|
||||||
|
broker.getConfig().put(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE, org.getDomains().iterator().next().getName());
|
||||||
testRealm().identityProviders().create(broker).close();
|
testRealm().identityProviders().create(broker).close();
|
||||||
getCleanup().addCleanup(testRealm().identityProviders().get(broker.getAlias())::remove);
|
getCleanup().addCleanup(testRealm().identityProviders().get(broker.getAlias())::remove);
|
||||||
testRealm().organizations().get(id).identityProviders().addIdentityProvider(broker.getAlias()).close();
|
testRealm().organizations().get(id).identityProviders().addIdentityProvider(broker.getAlias()).close();
|
||||||
|
@ -183,6 +187,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
||||||
Assert.assertTrue("We must be on correct realm right now",
|
Assert.assertTrue("We must be on correct realm right now",
|
||||||
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
|
||||||
log.debug("Updating info on updateAccount page");
|
log.debug("Updating info on updateAccount page");
|
||||||
|
assertFalse(driver.getPageSource().contains("kc.org"));
|
||||||
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), email, "Firstname", "Lastname");
|
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), email, "Firstname", "Lastname");
|
||||||
|
|
||||||
assertIsMember(email, organization);
|
assertIsMember(email, organization);
|
||||||
|
|
|
@ -21,6 +21,6 @@ import org.keycloak.common.Profile.Feature;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||||
|
|
||||||
@EnableFeature(Feature.ORGANIZATION)
|
@EnableFeature(Feature.ORGANIZATION)
|
||||||
public class OrganizationBrokerSelfRegistrationTest extends AbstractBrokerSelfRegistrationTest {
|
public class OrganizationOIDCBrokerSelfRegistrationTest extends AbstractBrokerSelfRegistrationTest {
|
||||||
|
|
||||||
}
|
}
|
|
@ -62,7 +62,7 @@
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
<#elseif section = "socialProviders" >
|
<#elseif section = "socialProviders" >
|
||||||
<#if realm.password && social?? && social.providers??>
|
<#if realm.password && social?? && social.providers?has_content>
|
||||||
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h4>${msg("identity-provider-login-label")}</h4>
|
<h4>${msg("identity-provider-login-label")}</h4>
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
<#elseif section = "socialProviders" >
|
<#elseif section = "socialProviders" >
|
||||||
<#if realm.password && social?? && social.providers??>
|
<#if realm.password && social?? && social.providers?has_content>
|
||||||
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h2>${msg("identity-provider-login-label")}</h2>
|
<h2>${msg("identity-provider-login-label")}</h2>
|
||||||
|
|
Loading…
Reference in a new issue