Organization member onboarding using the organization identity provider
Closes #28273 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
e7dd5c1991
commit
1e3837421e
21 changed files with 767 additions and 56 deletions
|
@ -226,6 +226,11 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return getAllStream().findAny().isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -18,9 +18,14 @@
|
|||
package org.keycloak.organization.jpa;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.organization.authentication.authenticators.broker.IdpOrganizationAuthenticatorFactory;
|
||||
import org.keycloak.organization.authentication.authenticators.browser.OrganizationAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmModel.RealmPostCreateEvent;
|
||||
import org.keycloak.models.RealmModel.RealmRemovedEvent;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.organization.OrganizationProviderFactory;
|
||||
|
@ -40,7 +45,7 @@ public class JpaOrganizationProviderFactory implements OrganizationProviderFacto
|
|||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
factory.register(this::handleRealmRemovedEvent);
|
||||
factory.register(this::handleEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,11 +58,66 @@ public class JpaOrganizationProviderFactory implements OrganizationProviderFacto
|
|||
return "jpa";
|
||||
}
|
||||
|
||||
private void handleRealmRemovedEvent(ProviderEvent event) {
|
||||
private void handleEvents(ProviderEvent event) {
|
||||
if (event instanceof RealmPostCreateEvent) {
|
||||
RealmModel realm = ((RealmPostCreateEvent) event).getCreatedRealm();
|
||||
configureAuthenticationFlows(realm);
|
||||
}
|
||||
if (event instanceof RealmRemovedEvent) {
|
||||
KeycloakSession session = ((RealmRemovedEvent) event).getKeycloakSession();
|
||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||
provider.removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void configureAuthenticationFlows(RealmModel realm) {
|
||||
addOrganizationFirstBrokerFlowStep(realm);
|
||||
addOrganizationBrowserFlowStep(realm);
|
||||
}
|
||||
|
||||
private void addOrganizationFirstBrokerFlowStep(RealmModel realm) {
|
||||
AuthenticationFlowModel firstBrokerLoginFlow = realm.getFirstBrokerLoginFlow();
|
||||
|
||||
if (firstBrokerLoginFlow == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (realm.getAuthenticationExecutionsStream(firstBrokerLoginFlow.getId())
|
||||
.map(AuthenticationExecutionModel::getAuthenticator)
|
||||
.anyMatch(IdpOrganizationAuthenticatorFactory.ID::equals)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(firstBrokerLoginFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator(IdpOrganizationAuthenticatorFactory.ID);
|
||||
execution.setPriority(50);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
}
|
||||
|
||||
public void addOrganizationBrowserFlowStep(RealmModel realm) {
|
||||
AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
|
||||
|
||||
if (browserFlow == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (realm.getAuthenticationExecutionsStream(browserFlow.getId())
|
||||
.map(AuthenticationExecutionModel::getAuthenticator)
|
||||
.anyMatch(OrganizationAuthenticatorFactory.ID::equals)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
|
||||
execution.setParentFlow(browserFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
execution.setAuthenticator(OrganizationAuthenticatorFactory.ID);
|
||||
execution.setPriority(26);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.ModelValidationException;
|
||||
import org.keycloak.models.OrganizationDomainModel;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
|
@ -128,6 +129,11 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderModel getIdentityProvider() {
|
||||
return provider.getIdentityProvider(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrganizationEntity getEntity() {
|
||||
return entity;
|
||||
|
|
|
@ -28,6 +28,8 @@ import jakarta.ws.rs.core.MultivaluedMap;
|
|||
import jakarta.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -162,4 +164,6 @@ public interface LoginFormsProvider extends Provider {
|
|||
LoginFormsProvider setExecution(String execution);
|
||||
|
||||
LoginFormsProvider setAuthContext(AuthenticationFlowContext context);
|
||||
|
||||
LoginFormsProvider setAttributeMapper(Function<Map<String, Object>, Map<String, Object>> configurer);
|
||||
}
|
||||
|
|
|
@ -132,4 +132,11 @@ public interface OrganizationProvider extends Provider {
|
|||
* @return {@code true} if the link was removed, {@code false} otherwise
|
||||
*/
|
||||
boolean removeIdentityProvider(OrganizationModel organization);
|
||||
|
||||
/**
|
||||
* Indicates if the current realm supports organization.
|
||||
*
|
||||
* @return {@code true} if organization is supported. Otherwise, returns {@code false}
|
||||
*/
|
||||
boolean isEnabled();
|
||||
}
|
||||
|
|
|
@ -39,4 +39,6 @@ public interface OrganizationModel {
|
|||
Stream<OrganizationDomainModel> getDomains();
|
||||
|
||||
void setDomains(Set<OrganizationDomainModel> domains);
|
||||
|
||||
IdentityProviderModel getIdentityProvider();
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ public class IdentityProviderAuthenticator implements Authenticator {
|
|||
}
|
||||
}
|
||||
|
||||
private void redirect(AuthenticationFlowContext context, String providerId) {
|
||||
protected void redirect(AuthenticationFlowContext context, String providerId) {
|
||||
Optional<IdentityProviderModel> idp = context.getRealm().getIdentityProvidersStream()
|
||||
.filter(IdentityProviderModel::isEnabled)
|
||||
.filter(identityProvider -> Objects.equals(providerId, identityProvider.getAlias()))
|
||||
|
|
|
@ -94,7 +94,9 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD;
|
||||
|
||||
|
@ -133,6 +135,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
protected UserModel user;
|
||||
|
||||
protected final Map<String, Object> attributes = new HashMap<>();
|
||||
private Function<Map<String, Object>, Map<String, Object>> attributeMapper;
|
||||
|
||||
public FreeMarkerLoginFormsProvider(KeycloakSession session) {
|
||||
this.session = session;
|
||||
|
@ -547,6 +550,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
*/
|
||||
protected Response processTemplate(Theme theme, String templateName, Locale locale) {
|
||||
try {
|
||||
Map<String, Object> attributes = Optional.ofNullable(attributeMapper).orElse(Function.identity()).apply(this.attributes);
|
||||
String result = freeMarker.processTemplate(attributes, templateName, theme);
|
||||
Response.ResponseBuilder builder = Response.status(status == null ? Response.Status.OK : status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
|
||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||
|
@ -903,11 +907,18 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setAuthContext(AuthenticationFlowContext context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setAttributeMapper(Function<Map<String, Object>, Map<String, Object>> mapper) {
|
||||
this.attributeMapper = mapper;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ import jakarta.ws.rs.core.Response;
|
|||
import jakarta.ws.rs.core.Response.Status;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
@ -70,7 +72,7 @@ public class OrganizationIdentityProviderResource {
|
|||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response addIdentityProvider(IdentityProviderRepresentation providerRep) {
|
||||
|
||||
IdentityProviderModel identityProvider = organizationProvider.getIdentityProvider(organization);
|
||||
IdentityProviderModel identityProvider = organization.getIdentityProvider();
|
||||
if (identityProvider != null) {
|
||||
throw ErrorResponse.error("Organization already assigned with an identity provider.", Status.BAD_REQUEST);
|
||||
}
|
||||
|
@ -101,8 +103,7 @@ public class OrganizationIdentityProviderResource {
|
|||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public IdentityProviderRepresentation getIdentityProvider() {
|
||||
IdentityProviderModel identityProvider = organizationProvider.getIdentityProvider(organization);
|
||||
return identityProvider == null ? null : toRepresentation(identityProvider);
|
||||
return Optional.ofNullable(organization.getIdentityProvider()).map(this::toRepresentation).orElse(null);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
@ -130,17 +131,21 @@ public class OrganizationIdentityProviderResource {
|
|||
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response update(IdentityProviderRepresentation providerRep) {
|
||||
public Response update(IdentityProviderRepresentation rep) {
|
||||
IdentityProviderModel identityProvider = getIdentityProviderModel();
|
||||
|
||||
Response response = getIdentityProviderResource(identityProvider).update(providerRep);
|
||||
if (!rep.getAlias().equals(identityProvider.getAlias())) {
|
||||
throw ErrorResponse.error("Identity provider not assigned to the organization.", Status.NOT_FOUND);
|
||||
}
|
||||
|
||||
Response response = getIdentityProviderResource(identityProvider).update(rep);
|
||||
|
||||
//update link between IdP and the organization if the update of IdP was successful and the IdP alias differs
|
||||
if (Status.NO_CONTENT.getStatusCode() == response.getStatus() &&
|
||||
! Objects.equals(identityProvider.getAlias(), providerRep.getAlias())) {
|
||||
! Objects.equals(identityProvider.getAlias(), rep.getAlias())) {
|
||||
|
||||
//get the updated IdP from session
|
||||
identityProvider = realm.getIdentityProviderByAlias(providerRep.getAlias());
|
||||
identityProvider = realm.getIdentityProviderByAlias(rep.getAlias());
|
||||
|
||||
String errorMessage;
|
||||
try {
|
||||
|
@ -167,10 +172,12 @@ public class OrganizationIdentityProviderResource {
|
|||
}
|
||||
|
||||
private IdentityProviderModel getIdentityProviderModel() {
|
||||
IdentityProviderModel identityProvider = organizationProvider.getIdentityProvider(organization);
|
||||
IdentityProviderModel identityProvider = organization.getIdentityProvider();
|
||||
|
||||
if (identityProvider == null) {
|
||||
throw ErrorResponse.error("Organization doesn't have assigned an identity provider.", Status.NOT_FOUND);
|
||||
}
|
||||
|
||||
return identityProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.authentication.authenticators.broker;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
|
||||
public class IdpOrganizationAuthenticator extends AbstractIdpAuthenticator {
|
||||
|
||||
@Override
|
||||
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
||||
OrganizationProvider provider = context.getSession().getProvider(OrganizationProvider.class);
|
||||
UserModel user = context.getUser();
|
||||
OrganizationModel organization = (OrganizationModel) context.getSession().getAttribute(OrganizationModel.class.getName());
|
||||
|
||||
if (organization == null) {
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityProviderModel expectedBroker = organization.getIdentityProvider();
|
||||
IdentityProviderModel currentBroker = brokerContext.getIdpConfig();
|
||||
|
||||
if (!expectedBroker.getAlias().equals(currentBroker.getAlias())) {
|
||||
context.failure(AuthenticationFlowError.ACCESS_DENIED);
|
||||
return;
|
||||
}
|
||||
|
||||
provider.addMember(organization, user);
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||
|
||||
if (!provider.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String domain = getEmailDomain(user.getEmail());
|
||||
|
||||
if (domain == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OrganizationModel organization = provider.getByDomainName(domain);
|
||||
|
||||
if (organization == null || provider.getIdentityProvider(organization) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
session.setAttribute(OrganizationModel.class.getName(), organization);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getEmailDomain(String email) {
|
||||
int domainSeparator = email.indexOf('@');
|
||||
|
||||
if (domainSeparator == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return email.substring(domainSeparator + 1);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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.authentication.authenticators.broker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
public class IdpOrganizationAuthenticatorFactory implements AuthenticatorFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String ID = "organization-broker";
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return new IdpOrganizationAuthenticator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return "organization";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Organization Member Link";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Adds a federated user as a member of an organization";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Scope config) {
|
||||
return Profile.isFeatureEnabled(Feature.ORGANIZATION);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.authentication.authenticators.browser;
|
||||
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
|
||||
public class OrganizationAuthenticator extends IdentityProviderAuthenticator {
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
public OrganizationAuthenticator(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
OrganizationProvider provider = getOrganizationProvider();
|
||||
|
||||
if (!provider.isEnabled()) {
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
|
||||
challenge(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
HttpRequest request = context.getHttpRequest();
|
||||
MultivaluedMap<String, String> parameters = request.getDecodedFormParameters();
|
||||
String username = parameters.getFirst(UserModel.USERNAME);
|
||||
|
||||
if (username == null) {
|
||||
challenge(context);
|
||||
return;
|
||||
}
|
||||
|
||||
String domain = getEmailDomain(username);
|
||||
OrganizationProvider provider = getOrganizationProvider();
|
||||
OrganizationModel organization = provider.getByDomainName(domain);
|
||||
|
||||
if (organization == null) {
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityProviderModel identityProvider = organization.getIdentityProvider();
|
||||
|
||||
if (identityProvider == null) {
|
||||
context.attempted();
|
||||
return;
|
||||
}
|
||||
|
||||
redirect(context, identityProvider.getAlias());
|
||||
}
|
||||
|
||||
private OrganizationProvider getOrganizationProvider() {
|
||||
return session.getProvider(OrganizationProvider.class);
|
||||
}
|
||||
|
||||
private void challenge (AuthenticationFlowContext context){
|
||||
context.challenge(context.form()
|
||||
.setAttributeMapper(attributes -> {
|
||||
// removes identity provider related attributes from forms
|
||||
attributes.remove("social");
|
||||
return attributes;
|
||||
})
|
||||
.createLoginUsername());
|
||||
}
|
||||
|
||||
private String getEmailDomain(String email) {
|
||||
int domainSeparator = email.indexOf('@');
|
||||
|
||||
if (domainSeparator == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return email.substring(domainSeparator + 1);
|
||||
}
|
||||
}
|
|
@ -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.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class OrganizationAuthenticatorFactory extends IdentityProviderAuthenticatorFactory implements EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String ID = "organization";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Organization Identity Provider Redirector";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "If organizations are enabled, automatically redirects users to the corresponding identity provider.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return new OrganizationAuthenticator(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Scope config) {
|
||||
return Profile.isFeatureEnabled(Feature.ORGANIZATION);
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFac
|
|||
org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
|
||||
org.keycloak.authentication.authenticators.broker.IdpAutoLinkAuthenticatorFactory
|
||||
org.keycloak.organization.authentication.authenticators.broker.IdpOrganizationAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
|
||||
org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.x509.X509ClientCertificateAuthenticatorFactory
|
||||
|
@ -51,3 +52,4 @@ org.keycloak.authentication.authenticators.access.DenyAccessAuthenticatorFactory
|
|||
org.keycloak.authentication.authenticators.access.AllowAccessAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.sessionlimits.UserSessionLimitsAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.browser.RecoveryAuthnCodesFormAuthenticatorFactory
|
||||
org.keycloak.organization.authentication.authenticators.browser.OrganizationAuthenticatorFactory
|
|
@ -87,6 +87,12 @@ public class LoginPage extends LanguageComboboxAwarePage {
|
|||
clickLink(submitButton);
|
||||
}
|
||||
|
||||
public void loginUsername(String username) {
|
||||
clearUsernameInputAndWaitIfNecessary();
|
||||
usernameInput.sendKeys(username);
|
||||
clickLink(submitButton);
|
||||
}
|
||||
|
||||
private void clearUsernameInputAndWaitIfNecessary() {
|
||||
try {
|
||||
usernameInput.clear();
|
||||
|
@ -145,6 +151,10 @@ public class LoginPage extends LanguageComboboxAwarePage {
|
|||
return passwordInput.getAttribute("value");
|
||||
}
|
||||
|
||||
public boolean isPasswordInputPresent() {
|
||||
return !driver.findElements(By.id("password")).isEmpty();
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelButton.click();
|
||||
}
|
||||
|
|
|
@ -75,10 +75,10 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
|||
client.setSecret(CLIENT_SECRET);
|
||||
|
||||
client.setRedirectUris(Collections.singletonList(getConsumerRoot() +
|
||||
"/auth/realms/" + REALM_CONS_NAME + "/broker/" + getIDPAlias() + "/endpoint/*"));
|
||||
"/auth/realms/" + consumerRealmName() + "/broker/" + getIDPAlias() + "/endpoint/*"));
|
||||
|
||||
client.setAdminUrl(getConsumerRoot() +
|
||||
"/auth/realms/" + REALM_CONS_NAME + "/broker/" + getIDPAlias() + "/endpoint");
|
||||
"/auth/realms/" + consumerRealmName() + "/broker/" + getIDPAlias() + "/endpoint");
|
||||
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+"));
|
||||
|
||||
|
@ -188,7 +188,7 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
|||
|
||||
@Override
|
||||
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
|
||||
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
|
||||
IdentityProviderRepresentation idp = createIdentityProvider(getIDPAlias(), IDP_OIDC_PROVIDER_ID);
|
||||
|
||||
Map<String, String> config = idp.getConfig();
|
||||
applyDefaultConfiguration(config, syncMode);
|
||||
|
|
|
@ -20,15 +20,20 @@ package org.keycloak.testsuite.organization.admin;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.admin.Users;
|
||||
import org.keycloak.testsuite.broker.KcOidcBrokerConfiguration;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -39,6 +44,52 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
|||
protected String memberEmail = "jdoe@neworg.org";
|
||||
protected String memberPassword = "password";
|
||||
|
||||
protected KcOidcBrokerConfiguration bc = new KcOidcBrokerConfiguration() {
|
||||
@Override
|
||||
public String consumerRealmName() {
|
||||
return TEST_REALM_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmRepresentation createProviderRealm() {
|
||||
RealmRepresentation providerRealm = super.createProviderRealm();
|
||||
|
||||
providerRealm.setClients(createProviderClients());
|
||||
providerRealm.setUsers(List.of(
|
||||
UserBuilder.create()
|
||||
.username(getUserLogin())
|
||||
.email(getUserEmail())
|
||||
.password(getUserPassword())
|
||||
.enabled(true).build())
|
||||
);
|
||||
|
||||
return providerRealm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserEmail() {
|
||||
return getUserLogin() + "@" + organizationName + ".org";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIDPAlias() {
|
||||
return "org-identity-provider";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
testRealm.getClients().addAll(bc.createConsumerClients());
|
||||
testRealm.setSmtpServer(null);
|
||||
super.configureTestRealm(testRealm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
testRealms.add(bc.createProviderRealm());
|
||||
super.addTestRealms(testRealms);
|
||||
}
|
||||
|
||||
protected OrganizationRepresentation createOrganization() {
|
||||
return createOrganization(organizationName);
|
||||
}
|
||||
|
@ -63,7 +114,10 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
|||
id = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
org.setId(id);
|
||||
testRealm().organizations().get(id).identityProvider().create(bc.setUpIdentityProvider()).close();
|
||||
|
||||
org = testRealm().organizations().get(id).toRepresentation();
|
||||
|
||||
getCleanup().addCleanup(() -> testRealm().organizations().get(id).delete().close());
|
||||
|
||||
return org;
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.organization.admin;
|
||||
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
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.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
@EnableFeature(Feature.ORGANIZATION)
|
||||
public class OrganizationBrokerSelfRegistrationTest extends AbstractOrganizationTest {
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected IdpConfirmLinkPage idpConfirmLinkPage;
|
||||
|
||||
@Page
|
||||
protected UpdateAccountInformationPage updateAccountInformationPage;
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Test
|
||||
public void testBrokerRegistration() {
|
||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||
assertBrokerRegistration(organization);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultAuthenticationMechanismIfNotOrganizationMember() {
|
||||
testRealm().organizations().get(createOrganization().getId());
|
||||
oauth.clientId("broker-app");
|
||||
|
||||
// login with email only
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
log.debug("Logging in");
|
||||
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||
loginPage.loginUsername("user@noorg.org");
|
||||
|
||||
// check if the login page is shown
|
||||
Assert.assertTrue(loginPage.isUsernameInputPresent());
|
||||
Assert.assertTrue(loginPage.isPasswordInputPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkExistingAccount() {
|
||||
// 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());
|
||||
oauth.clientId("broker-app");
|
||||
|
||||
// login with email only
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
log.debug("Logging in");
|
||||
loginPage.loginUsername(bc.getUserEmail());
|
||||
|
||||
// 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.getUserLogin(), 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 testMemberAlreadyExists() {
|
||||
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
|
||||
|
||||
// add the member for the first time
|
||||
assertBrokerRegistration(organization);
|
||||
|
||||
// logout to force the user to authenticate again
|
||||
UserRepresentation account = getUserRepresentation(bc.getUserEmail());
|
||||
realmsResouce().realm(bc.consumerRealmName()).users().get(account.getId()).logout();
|
||||
|
||||
// login with email only
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
log.debug("Logging in");
|
||||
loginPage.loginUsername(bc.getUserEmail());
|
||||
|
||||
// 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 automatically redirects to the app as the account already exists
|
||||
loginPage.login(bc.getUserEmail(), bc.getUserPassword());
|
||||
appPage.assertCurrent();
|
||||
assertIsMember(bc.getUserEmail(), organization);
|
||||
}
|
||||
|
||||
private void assertBrokerRegistration(OrganizationResource organization) {
|
||||
// login with email only
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
log.debug("Logging in");
|
||||
Assert.assertFalse(loginPage.isPasswordInputPresent());
|
||||
Assert.assertFalse(loginPage.isSocialButtonPresent(bc.getIDPAlias()));
|
||||
loginPage.loginUsername(bc.getUserEmail());
|
||||
|
||||
// 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.getUserLogin(), bc.getUserEmail(), "Firstname", "Lastname");
|
||||
|
||||
assertIsMember(bc.getUserEmail(), organization);
|
||||
}
|
||||
|
||||
private void assertIsMember(String userEmail, OrganizationResource organization) {
|
||||
UserRepresentation account = getUserRepresentation(userEmail);
|
||||
UserRepresentation member = organization.members().member(account.getId()).toRepresentation();
|
||||
Assert.assertEquals(account.getId(), member.getId());
|
||||
}
|
||||
|
||||
private UserRepresentation getUserRepresentation(String userEmail) {
|
||||
UsersResource users = adminClient.realm(bc.consumerRealmName()).users();
|
||||
List<UserRepresentation> reps = users.searchByEmail(userEmail, true);
|
||||
Assert.assertFalse(reps.isEmpty());
|
||||
Assert.assertEquals(1, reps.size());
|
||||
return reps.get(0);
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.Before;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
|
@ -34,35 +34,41 @@ import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
|||
@EnableFeature(Feature.ORGANIZATION)
|
||||
public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
||||
|
||||
private final String idpAlias = "org-identity-provider";
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
OrganizationRepresentation organization = createOrganization();
|
||||
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(organization.getId()).identityProvider();
|
||||
IdentityProviderRepresentation idpRepresentation = orgIdPResource.toRepresentation();
|
||||
assertThat(idpRepresentation.getAlias(), equalTo(bc.getIDPAlias()));
|
||||
|
||||
@Before
|
||||
public void addCleanups() {
|
||||
addCleanupIdP(idpAlias);
|
||||
String displayName = "My Org Broker";
|
||||
//update
|
||||
idpRepresentation.setDisplayName(displayName);
|
||||
try (Response response = orgIdPResource.update(idpRepresentation)) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.NO_CONTENT.getStatusCode()));
|
||||
}
|
||||
assertThat(orgIdPResource.toRepresentation().getDisplayName(), equalTo(displayName));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCRUD() {
|
||||
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(createOrganization().getId()).identityProvider();
|
||||
public void testFailUpdateAlias() {
|
||||
OrganizationRepresentation organization = createOrganization();
|
||||
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(organization.getId()).identityProvider();
|
||||
IdentityProviderRepresentation idpRepresentation = orgIdPResource.toRepresentation();
|
||||
assertThat(idpRepresentation.getAlias(), equalTo(bc.getIDPAlias()));
|
||||
|
||||
//create, read
|
||||
IdentityProviderRepresentation idpRepresentation = createRep(idpAlias, "oidc");
|
||||
try (Response response = orgIdPResource.create(idpRepresentation)) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
|
||||
}
|
||||
idpRepresentation = orgIdPResource.toRepresentation();
|
||||
assertThat(idpRepresentation.getAlias(), equalTo(idpAlias));
|
||||
|
||||
String updatedIdpAlias = "updated-org-identity-provider";
|
||||
//update
|
||||
idpRepresentation.setAlias(updatedIdpAlias);
|
||||
idpRepresentation.setAlias("should-fail");
|
||||
try (Response response = orgIdPResource.update(idpRepresentation)) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.NO_CONTENT.getStatusCode()));
|
||||
addCleanupIdP(updatedIdpAlias);
|
||||
assertThat(response.getStatus(), equalTo(Status.NOT_FOUND.getStatusCode()));
|
||||
}
|
||||
assertThat(orgIdPResource.toRepresentation().getAlias(), equalTo(updatedIdpAlias));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
OrganizationRepresentation organization = createOrganization();
|
||||
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(organization.getId()).identityProvider();
|
||||
|
||||
//delete
|
||||
try (Response response = orgIdPResource.delete()) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.NO_CONTENT.getStatusCode()));
|
||||
}
|
||||
|
@ -73,10 +79,7 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
|||
public void tryCreateSecondIdp() {
|
||||
OrganizationIdentityProviderResource orgIdPResource = testRealm().organizations().get(createOrganization().getId()).identityProvider();
|
||||
|
||||
IdentityProviderRepresentation idpRepresentation = createRep(idpAlias, "oidc");
|
||||
try (Response response = orgIdPResource.create(idpRepresentation)) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
|
||||
}
|
||||
IdentityProviderRepresentation idpRepresentation = orgIdPResource.toRepresentation();
|
||||
|
||||
idpRepresentation.setAlias("another-idp");
|
||||
try (Response response = orgIdPResource.create(idpRepresentation)) {
|
||||
|
@ -89,18 +92,11 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
|||
OrganizationRepresentation orgRep = createOrganization();
|
||||
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
OrganizationIdentityProviderResource orgIdPResource = orgResource.identityProvider();
|
||||
|
||||
IdentityProviderRepresentation idpRepresentation = createRep(idpAlias, "oidc");
|
||||
try (Response response = orgIdPResource.create(idpRepresentation)) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
|
||||
}
|
||||
|
||||
try (Response response = orgResource.delete()) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.NO_CONTENT.getStatusCode()));
|
||||
}
|
||||
|
||||
testRealm().identityProviders().get(idpAlias).toRepresentation();
|
||||
testRealm().identityProviders().get(bc.getIDPAlias()).toRepresentation();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -110,7 +106,7 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
|||
|
||||
OrganizationIdentityProviderResource orgIdPResource = orgResource.identityProvider();
|
||||
|
||||
IdentityProviderRepresentation idpRepresentation = createRep(idpAlias, "oidc");
|
||||
IdentityProviderRepresentation idpRepresentation = createRep("some-broker", "oidc");
|
||||
//create IdP in realm not bound to Org
|
||||
testRealm().identityProviders().create(idpRepresentation).close();
|
||||
|
||||
|
@ -118,7 +114,10 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
|||
assertThat(response.getStatus(), equalTo(Response.Status.NOT_FOUND.getStatusCode()));
|
||||
}
|
||||
try (Response response = orgIdPResource.delete()) {
|
||||
assertThat(response.getStatus(), equalTo(Response.Status.NOT_FOUND.getStatusCode()));
|
||||
assertThat(response.getStatus(), equalTo(Status.NO_CONTENT.getStatusCode()));
|
||||
}
|
||||
try (Response response = orgIdPResource.delete()) {
|
||||
assertThat(response.getStatus(), equalTo(Status.NOT_FOUND.getStatusCode()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,8 +130,4 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
|
|||
idp.setEnabled(true);
|
||||
return idp;
|
||||
}
|
||||
|
||||
private void addCleanupIdP(String alias) {
|
||||
getCleanup().addCleanup(() -> testRealm().identityProviders().get(alias).remove());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
</div>
|
||||
</#if>
|
||||
<#elseif section = "socialProviders" >
|
||||
<#if realm.password && social.providers??>
|
||||
<#if realm.password && social?? && social.providers??>
|
||||
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
||||
<hr/>
|
||||
<h4>${msg("identity-provider-login-label")}</h4>
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
</div>
|
||||
</#if>
|
||||
<#elseif section = "socialProviders" >
|
||||
<#if realm.password && social.providers??>
|
||||
<#if realm.password && social?? && social.providers??>
|
||||
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
||||
<hr/>
|
||||
<h2>${msg("identity-provider-login-label")}</h2>
|
||||
|
|
Loading…
Reference in a new issue