KEYCLOAK-7270 - Support for automatically linking brokered identities
This commit is contained in:
parent
62c1ffcb52
commit
c4a651bcac
5 changed files with 243 additions and 0 deletions
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.broker;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import static org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator.getExistingUser;
|
||||
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:Ryan.Slominski@gmail.com">Ryan Slominski</a>
|
||||
*/
|
||||
public class IdpAutoLinkAuthenticator extends AbstractIdpAuthenticator {
|
||||
|
||||
private static Logger logger = Logger.getLogger(IdpAutoLinkAuthenticator.class);
|
||||
|
||||
@Override
|
||||
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
||||
KeycloakSession session = context.getSession();
|
||||
RealmModel realm = context.getRealm();
|
||||
AuthenticationSessionModel authSession = context.getAuthenticationSession();
|
||||
|
||||
UserModel existingUser = getExistingUser(session, realm, authSession);
|
||||
|
||||
logger.debugf("User '%s' will auto link with identity provider '%s' . Identity provider username is '%s' ", existingUser.getUsername(),
|
||||
brokerContext.getIdpConfig().getAlias(), brokerContext.getUsername());
|
||||
|
||||
context.setUser(existingUser);
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
|
||||
authenticateImpl(context, serializedCtx, brokerContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.broker;
|
||||
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:Ryan.Slominski@gmail.com">Ryan Slominski</a>
|
||||
*/
|
||||
public class IdpAutoLinkAuthenticatorFactory implements AuthenticatorFactory {
|
||||
public static final String PROVIDER_ID = "idp-auto-link";
|
||||
static IdpAutoLinkAuthenticator SINGLETON = new IdpAutoLinkAuthenticator();
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return "autoLink";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Automatically link brokered account";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Automatically link brokered account without any verification";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthentic
|
|||
org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
|
||||
org.keycloak.authentication.authenticators.broker.IdpAutoLinkAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
|
||||
org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.x509.X509ClientCertificateAuthenticatorFactory
|
||||
|
|
|
@ -166,6 +166,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
"You will be approved if you send query string parameter 'foo' with expected value.");
|
||||
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-auto-link", "Automatically link brokered account", "Automatically link brokered account without any verification");
|
||||
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 " +
|
||||
|
|
|
@ -39,6 +39,9 @@ import java.util.Set;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import static org.keycloak.testsuite.broker.AbstractFirstBrokerLoginTest.APP_REALM_ID;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -147,6 +150,77 @@ public class OIDCFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
|
|||
}, APP_REALM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user can link federated identity with existing brokered
|
||||
* account without prompt (KEYCLOAK-7270).
|
||||
*/
|
||||
@Test
|
||||
public void testAutoLinkAccountWithBroker() throws Exception {
|
||||
final String originalFirstBrokerLoginFlowId = getRealm().getIdentityProviderByAlias(getProviderId()).getFirstBrokerLoginFlowId();
|
||||
|
||||
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
AuthenticationFlowModel newFlow = new AuthenticationFlowModel();
|
||||
newFlow.setAlias("AutoLink");
|
||||
newFlow.setDescription("AutoLink");
|
||||
newFlow.setProviderId("basic-flow");
|
||||
newFlow.setBuiltIn(false);
|
||||
newFlow.setTopLevel(true);
|
||||
newFlow = appRealm.addAuthenticationFlow(newFlow);
|
||||
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
execution.setAuthenticator("idp-create-user-if-unique");
|
||||
execution.setPriority(1);
|
||||
execution.setParentFlow(newFlow.getId());
|
||||
execution = appRealm.addAuthenticatorExecution(execution);
|
||||
|
||||
AuthenticationExecutionModel execution2 = new AuthenticationExecutionModel();
|
||||
execution2.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
execution2.setAuthenticatorFlow(false);
|
||||
execution2.setAuthenticator("idp-auto-link");
|
||||
execution2.setPriority(2);
|
||||
execution2.setParentFlow(newFlow.getId());
|
||||
execution2 = appRealm.addAuthenticatorExecution(execution2);
|
||||
|
||||
IdentityProviderModel idp = appRealm.getIdentityProviderByAlias(getProviderId());
|
||||
idp.setFirstBrokerLoginFlowId(newFlow.getId());
|
||||
appRealm.updateIdentityProvider(idp);
|
||||
|
||||
}
|
||||
}, APP_REALM_ID);
|
||||
|
||||
// login through OIDC broker
|
||||
loginIDP("pedroigor");
|
||||
|
||||
// authenticated and redirected to app. User is linked with identity provider
|
||||
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
|
||||
UserModel federatedUser = getFederatedUser();
|
||||
|
||||
assertNotNull(federatedUser);
|
||||
assertEquals("pedroigor", federatedUser.getUsername());
|
||||
assertEquals("psilva@redhat.com", federatedUser.getEmail());
|
||||
|
||||
RealmModel realmWithBroker = getRealm();
|
||||
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker);
|
||||
assertEquals(1, federatedIdentities.size());
|
||||
|
||||
for (FederatedIdentityModel link : federatedIdentities) {
|
||||
Assert.assertEquals("pedroigor", link.getUserName());
|
||||
Assert.assertTrue(link.getIdentityProvider().equals(getProviderId()));
|
||||
}
|
||||
|
||||
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
IdentityProviderModel idp = appRealm.getIdentityProviderByAlias(getProviderId());
|
||||
idp.setFirstBrokerLoginFlowId(originalFirstBrokerLoginFlowId);
|
||||
appRealm.updateIdentityProvider(idp);
|
||||
}
|
||||
}, APP_REALM_ID);
|
||||
}
|
||||
|
||||
// KEYCLOAK-5936
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue