Merge pull request #3205 from stianst/KEYCLOAK-3342

KEYCLOAK-3342 Add Identity Provider authenticator
This commit is contained in:
Stian Thorgersen 2016-09-08 08:40:32 +02:00 committed by GitHub
commit f726caea9b
36 changed files with 489 additions and 156 deletions

View file

@ -123,10 +123,15 @@ public class IdentityProviderRepresentation {
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
}
/**
* @deprecated Replaced by configuration option in identity provider authenticator
*/
@Deprecated
public boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
@Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}

View file

@ -23,11 +23,6 @@ package org.keycloak.migration;
* @version $Revision: 1 $
*/
public interface MigrationModel {
/**
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
*/
String LATEST_VERSION = "2.1.0";
String getStoredVersion();
void setStoredVersion(String version);
}

View file

@ -18,6 +18,7 @@
package org.keycloak.migration;
import org.jboss.logging.Logger;
import org.keycloak.migration.migrators.MigrateTo1_2_0;
import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0;
import org.keycloak.migration.migrators.MigrateTo1_5_0;
@ -28,7 +29,8 @@ import org.keycloak.migration.migrators.MigrateTo1_9_0;
import org.keycloak.migration.migrators.MigrateTo1_9_2;
import org.keycloak.migration.migrators.MigrateTo2_0_0;
import org.keycloak.migration.migrators.MigrateTo2_1_0;
import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
import org.keycloak.migration.migrators.MigrateTo2_2_0;
import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession;
/**
@ -38,82 +40,41 @@ import org.keycloak.models.KeycloakSession;
public class MigrationModelManager {
private static Logger logger = Logger.getLogger(MigrationModelManager.class);
private static final Migration[] migrations = {
new MigrateTo1_2_0(),
new MigrateTo1_3_0(),
new MigrateTo1_4_0(),
new MigrateTo1_5_0(),
new MigrateTo1_6_0(),
new MigrateTo1_7_0(),
new MigrateTo1_8_0(),
new MigrateTo1_9_0(),
new MigrateTo1_9_2(),
new MigrateTo2_0_0(),
new MigrateTo2_1_0(),
new MigrateTo2_2_0(),
};
public static void migrate(KeycloakSession session) {
ModelVersion latest = migrations[migrations.length-1].getVersion();
MigrationModel model = session.realms().getMigrationModel();
String storedVersion = model.getStoredVersion();
if (MigrationModel.LATEST_VERSION.equals(storedVersion)) return;
ModelVersion stored = null;
if (storedVersion != null) {
stored = new ModelVersion(storedVersion);
if (model.getStoredVersion() != null) {
stored = new ModelVersion(model.getStoredVersion());
if (latest.equals(stored)) {
return;
}
}
if (stored == null || stored.lessThan(MigrationTo1_2_0_CR1.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.2.0.CR1 updates");
for (Migration m : migrations) {
if (stored == null || stored.lessThan(m.getVersion())) {
if (stored != null) {
logger.debugf("Migrating older model to %s", m.getVersion());
}
m.migrate(session);
}
new MigrationTo1_2_0_CR1().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_3_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.3.0 updates");
}
new MigrateTo1_3_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_4_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.4.0 updates");
}
new MigrateTo1_4_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_5_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.5.0 updates");
}
new MigrateTo1_5_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_6_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.6.0 updates");
}
new MigrateTo1_6_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_7_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.7.0 updates");
}
new MigrateTo1_7_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_8_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.8.0 updates");
}
new MigrateTo1_8_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_9_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.9.0 updates");
}
new MigrateTo1_9_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo1_9_2.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 1.9.2 updates");
}
new MigrateTo1_9_2().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo2_0_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 2.0.0 updates");
}
new MigrateTo2_0_0().migrate(session);
}
if (stored == null || stored.lessThan(MigrateTo2_1_0.VERSION)) {
if (stored != null) {
logger.debug("Migrating older model to 2.1.0 updates");
}
new MigrateTo2_1_0().migrate(session);
}
model.setStoredVersion(MigrationModel.LATEST_VERSION);
model.setStoredVersion(latest.toString());
}
}

View file

@ -98,4 +98,19 @@ public class ModelVersion {
if (comp < 0) return true;
return false;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ModelVersion)) {
return false;
}
ModelVersion v = (ModelVersion) obj;
return v.getMajor() == major && v.getMinor() == minor && v.getMicro() != micro;
}
@Override
public String toString() {
return major + "." + minor + "." + micro;
}
}

View file

@ -32,8 +32,13 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MigrationTo1_2_0_CR1 {
public static final ModelVersion VERSION = new ModelVersion("1.2.0.CR1");
public class MigrateTo1_2_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.2.0");
@Override
public ModelVersion getVersion() {
return VERSION;
}
public void setupBrokerService(RealmModel realm) {
ClientModel client = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID);

View file

@ -37,9 +37,13 @@ import javax.naming.directory.SearchControls;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MigrateTo1_3_0 {
public class MigrateTo1_3_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.3.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();

View file

@ -34,9 +34,13 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MigrateTo1_4_0 {
public class MigrateTo1_4_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.4.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {

View file

@ -32,9 +32,13 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MigrateTo1_5_0 {
public class MigrateTo1_5_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.5.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {

View file

@ -27,10 +27,14 @@ import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo1_6_0 {
public class MigrateTo1_6_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.6.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
MigrationProvider provider = session.getProvider(MigrationProvider.class);

View file

@ -31,10 +31,14 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo1_7_0 {
public class MigrateTo1_7_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.7.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {

View file

@ -30,10 +30,14 @@ import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo1_8_0 {
public class MigrateTo1_8_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.8.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {

View file

@ -33,10 +33,14 @@ import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo1_9_0 {
public class MigrateTo1_9_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.9.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
if (realm != null && realm.getDisplayNameHtml() != null && realm.getDisplayNameHtml().equals("<strong>Keycloak</strong>")) {

View file

@ -25,10 +25,14 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MigrateTo1_9_2 {
public class MigrateTo1_9_2 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.9.2");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
if (realm.getBrowserSecurityHeaders() != null) {

View file

@ -23,10 +23,14 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
public class MigrateTo2_0_0 {
public class MigrateTo2_0_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("2.0.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateAuthorizationServices(realm);

View file

@ -38,9 +38,13 @@ import java.util.stream.Collectors;
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
public class MigrateTo2_1_0 {
public class MigrateTo2_1_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("2.1.0");
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateDefaultRequiredAction(realm);

View file

@ -0,0 +1,59 @@
/*
* Copyright 2016 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.migration.migrators;
import org.jboss.logging.Logger;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import java.util.HashMap;
import java.util.Map;
public class MigrateTo2_2_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("2.2.0");
private static final Logger LOG = Logger.getLogger(MigrateTo2_2_0.class);
public ModelVersion getVersion() {
return VERSION;
}
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
addIdentityProviderAuthenticator(realm);
}
}
private void addIdentityProviderAuthenticator(RealmModel realm) {
String defaultProvider = null;
for (IdentityProviderModel provider : realm.getIdentityProviders()) {
if (provider.isEnabled() && provider.isAuthenticateByDefault()) {
defaultProvider = provider.getAlias();
break;
}
}
DefaultAuthenticationFlows.addIdentityProviderAuthenticator(realm, defaultProvider);
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2016 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.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface Migration {
void migrate(KeycloakSession session);
ModelVersion getVersion();
}

View file

@ -120,10 +120,12 @@ public class IdentityProviderModel implements Serializable {
this.storeToken = storeToken;
}
@Deprecated
public boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
@Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}

View file

@ -276,6 +276,7 @@ public class DefaultAuthenticationFlows {
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
addIdentityProviderAuthenticator(realm, null);
AuthenticationFlowModel forms = new AuthenticationFlowModel();
forms.setTopLevel(false);
@ -317,6 +318,45 @@ public class DefaultAuthenticationFlows {
realm.addAuthenticatorExecution(execution);
}
public static void addIdentityProviderAuthenticator(RealmModel realm, String defaultProvider) {
String browserFlowId = null;
for (AuthenticationFlowModel f : realm.getAuthenticationFlows()) {
if (f.getAlias().equals(DefaultAuthenticationFlows.BROWSER_FLOW)) {
browserFlowId = f.getId();
break;
}
}
if (browserFlowId != null) {
for (AuthenticationExecutionModel e : realm.getAuthenticationExecutions(browserFlowId)) {
if ("identity-provider-redirector".equals(e.getAuthenticator())) {
return;
}
}
AuthenticationExecutionModel execution;
execution = new AuthenticationExecutionModel();
execution.setParentFlow(browserFlowId);
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("identity-provider-redirector");
execution.setPriority(25);
execution.setAuthenticatorFlow(false);
if (defaultProvider != null) {
AuthenticatorConfigModel configModel = new AuthenticatorConfigModel();
Map<String, String> config = new HashMap<>();
config.put("defaultProvider", defaultProvider);
configModel.setConfig(config);
configModel = realm.addAuthenticatorConfig(configModel);
execution.setAuthenticatorConfig(configModel.getId());
}
realm.addAuthenticatorExecution(execution);
}
}
public static void clientAuthFlow(RealmModel realm) {
AuthenticationFlowModel clients = new AuthenticationFlowModel();
clients.setAlias(CLIENT_AUTHENTICATION_FLOW);

View file

@ -565,6 +565,19 @@ public class RepresentationToModel {
if (newRealm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW) == null) {
DefaultAuthenticationFlows.firstBrokerLoginFlow(newRealm, true);
}
// Added in 2.2
String defaultProvider = null;
if (rep.getIdentityProviders() != null) {
for (IdentityProviderRepresentation i : rep.getIdentityProviders()) {
if (i.isEnabled() && i.isAuthenticateByDefault()) {
defaultProvider = i.getProviderId();
break;
}
}
}
DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider);
}
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {

View file

@ -0,0 +1,103 @@
/*
* Copyright 2016 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.authentication.authenticators.browser;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.ClientSessionCode;
import javax.ws.rs.core.Response;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class IdentityProviderAuthenticator implements Authenticator {
private static final Logger LOG = Logger.getLogger(IdentityProviderAuthenticator.class);
@Override
public void authenticate(AuthenticationFlowContext context) {
if (context.getUriInfo().getQueryParameters().containsKey(AdapterConstants.KC_IDP_HINT)) {
String providerId = context.getUriInfo().getQueryParameters().getFirst(AdapterConstants.KC_IDP_HINT);
if (providerId == null || providerId.equals("")) {
LOG.tracef("Skipping: kc_idp_hint query parameter is empty");
context.attempted();
} else {
LOG.tracef("Redirecting: %s set to %s", AdapterConstants.KC_IDP_HINT, providerId);
redirect(context, providerId);
}
} else if (context.getAuthenticatorConfig() != null && context.getAuthenticatorConfig().getConfig().containsKey(IdentityProviderAuthenticatorFactory.DEFAULT_PROVIDER)) {
String defaultProvider = context.getAuthenticatorConfig().getConfig().get(IdentityProviderAuthenticatorFactory.DEFAULT_PROVIDER);
LOG.tracef("Redirecting: default provider set to %s", defaultProvider);
redirect(context, defaultProvider);
} else {
LOG.tracef("No default provider set or %s query parameter provided", AdapterConstants.KC_IDP_HINT);
context.attempted();
}
}
private void redirect(AuthenticationFlowContext context, String providerId) {
List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
String accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode();
Response response = Response.temporaryRedirect(
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
.build();
LOG.debugf("Redirecting to %s", providerId);
context.forceChallenge(response);
return;
}
}
LOG.warnf("Provider not found or not enabled for realm %s", providerId);
context.attempted();
}
@Override
public void action(AuthenticationFlowContext context) {
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
@Override
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright 2016 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.authentication.authenticators.browser;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.Collections;
import java.util.List;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory {
protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED
};
protected static final String DEFAULT_PROVIDER = "defaultProvider";
@Override
public String getDisplayType() {
return "Identity Provider Redirector";
}
@Override
public String getReferenceCategory() {
return null;
}
@Override
public boolean isConfigurable() {
return true;
}
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public boolean isUserSetupAllowed() {
return true;
}
@Override
public String getHelpText() {
return "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
ProviderConfigProperty rep = new ProviderConfigProperty(DEFAULT_PROVIDER, "Default Identity Provider", "To automatically redirect to an identity provider set to the alias of the identity provider", STRING_TYPE, null);
return Collections.singletonList(rep);
}
@Override
public Authenticator create(KeycloakSession session) {
return new IdentityProviderAuthenticator();
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "identity-provider-redirector";
}
}

View file

@ -17,8 +17,6 @@
package org.keycloak.protocol;
import java.util.List;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@ -31,14 +29,11 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService;
/**
@ -95,15 +90,6 @@ public abstract class AuthorizationEndpointBase {
* @return response to be returned to the browser
*/
protected Response handleBrowserAuthenticationRequest(ClientSessionModel clientSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) {
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled() && identityProvider.isAuthenticateByDefault()) {
// TODO if we are isPassive we should propagate this flag to default identity provider also if possible
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode());
}
}
AuthenticationFlowModel flow = getAuthenticationFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
@ -147,11 +133,4 @@ public abstract class AuthorizationEndpointBase {
return realm.getBrowserFlow();
}
protected Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
logger.debug("Automatically redirect to identity provider: " + providerId);
return Response.temporaryRedirect(
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
.build();
}
}

View file

@ -29,11 +29,9 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -46,7 +44,6 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
@ -313,19 +310,6 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
}
private Response buildAuthorizationCodeAuthorizationResponse() {
String idpHint = request.getIdpHint();
if (idpHint != null && !"".equals(idpHint)) {
IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
if (identityProviderModel == null) {
return session.getProvider(LoginFormsProvider.class)
.setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
.createErrorPage();
}
return buildRedirectToIdentityProvider(idpHint, new ClientSessionCode(realm, clientSession).getCode());
}
this.event.event(EventType.LOGIN);
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);

View file

@ -20,6 +20,7 @@ org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory
org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory
org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory
org.keycloak.authentication.authenticators.browser.SpnegoAuthenticatorFactory
org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory
org.keycloak.authentication.authenticators.directgrant.ValidateOTP
org.keycloak.authentication.authenticators.directgrant.ValidatePassword
org.keycloak.authentication.authenticators.directgrant.ValidateUsername
@ -33,4 +34,4 @@ org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFac
org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator
org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator

View file

@ -104,7 +104,7 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
}
void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) {
Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias());
Assert.assertEquals("Execution flowAlias - " + actual.getFlowAlias(), expected.getFlowAlias(), actual.getFlowAlias());
Assert.assertEquals("Execution authenticator - " + actual.getAuthenticator(), expected.getAuthenticator(), actual.getAuthenticator());
Assert.assertEquals("Execution userSetupAllowed - " + actual.getAuthenticator(), expected.isUserSetupAllowed(), actual.isUserSetupAllowed());
Assert.assertEquals("Execution authenticatorFlow - " + actual.getAuthenticator(), expected.isAutheticatorFlow(), actual.isAutheticatorFlow());

View file

@ -94,7 +94,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// we'll need auth-cookie later
AuthenticationExecutionInfoRepresentation authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 4, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
@ -164,7 +164,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// Note: there is no checking in addExecution if requirement is one of requirementChoices
// Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 3, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
}
@Test

View file

@ -203,7 +203,7 @@ public class FlowTest extends AbstractAuthenticationTest {
// adjust expected values before comparing
browser.setAlias("Copy of browser");
browser.setBuiltIn(false);
browser.getAuthenticationExecutions().get(2).setFlowAlias("Copy of browser forms");
browser.getAuthenticationExecutions().get(3).setFlowAlias("Copy of browser forms");
compareFlows(browser, copyOfBrowser);
// get new flow directly and compare

View file

@ -125,12 +125,14 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10);
addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20);
addExecExport(flow, null, false, "identity-provider-redirector", false, null, ALTERNATIVE, 25);
addExecExport(flow, "forms", false, null, true, null, ALTERNATIVE, 30);
List<AuthenticationExecutionInfoRepresentation> execs = new LinkedList<>();
addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
addExecInfo(execs, "forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
addExecInfo(execs, "Identity Provider Redirector", "identity-provider-redirector", true, 0, 2, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
addExecInfo(execs, "forms", null, false, 0, 3, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED});
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
expected.add(new FlowExecutions(flow, execs));

View file

@ -146,6 +146,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
addProviderInfo(result, "direct-grant-validate-username", "Username Validation",
"Validates the username supplied as a 'username' form parameter in direct grant request");
addProviderInfo(result, "http-basic-authenticator", "HTTP Basic Authentication", "Validates username and password from Authorization HTTP header");
addProviderInfo(result, "identity-provider-redirector", "Identity Provider Redirector", "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter");
addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " +
"to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict");
addProviderInfo(result, "idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account " +

View file

@ -61,3 +61,5 @@ log4j.logger.org.hibernate=off
log4j.logger.org.jboss.resteasy=warn
log4j.logger.org.apache.directory.api=warn
log4j.logger.org.apache.directory.server.core=warn
log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace

View file

@ -20,19 +20,19 @@ package org.keycloak.testsuite.broker;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.keycloak.testsuite.KeycloakServer;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
@ -94,6 +94,16 @@ public class IdentityProviderHintTest {
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
assertEquals("Could not find an identity provider with the identifier.", this.driver.findElement(By.className("instruction")).getText());
System.out.println(driver.getPageSource());
assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
}
private AuthenticationExecutionInfoRepresentation findExecution(RealmResource realm) {
for (AuthenticationExecutionInfoRepresentation e : realm.flows().getExecutions("browser")) {
if (e.getProviderId().equals("identity-provider-redirector")) {
return e;
}
}
return null;
}
}

View file

@ -74,4 +74,7 @@ log4j.logger.org.apache.directory.server.core=warn
log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
# Enable to view HttpClient connection pool activity
#log4j.logger.org.apache.http.impl.conn=debug
#log4j.logger.org.apache.http.impl.conn=debug
# Enable to view details from identity provider authenticator
# log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace

View file

@ -34,13 +34,6 @@
</div>
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="authenticateByDefault">{{:: 'authenticate-by-default' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'identity-provider.authenticate-by-default.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="storeToken">{{:: 'store-tokens' | translate}}</label>
<div class="col-md-6">

View file

@ -31,13 +31,6 @@
</div>
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="authenticateByDefault">{{:: 'authenticate-by-default' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'identity-provider.authenticate-by-default.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="storeToken">{{:: 'store-tokens' | translate}}</label>
<div class="col-md-6">

View file

@ -70,13 +70,6 @@
</div>
<kc-tooltip>{{:: 'trust-email.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="authenticateByDefault">{{:: 'authenticate-by-default' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'identity-provider.authenticate-by-default.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="guiOrder">{{:: 'gui-order' | translate}}</label>
<div class="col-md-6">