KEYCLOAK-1053 - social login do not fail when email validation is

enabled for realm but email is not provided by social login provider
(eg. Twitter)
This commit is contained in:
Vlastimil Elias 2015-03-19 17:16:27 +01:00
parent 3912c60ebc
commit c0cf537654
9 changed files with 109 additions and 42 deletions

View file

@ -41,7 +41,6 @@ public interface Errors {
String NOT_ALLOWED = "not_allowed";
String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists";
String FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING = "federated_identity_registration_email_missing";
String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
String SSL_REQUIRED = "ssl_required";

View file

@ -79,7 +79,6 @@ invalidTotpMessage=Ung
usernameExistsMessage=Benutzermane exisitert bereits.
emailExistsMessage=E-Mail existiert bereits.
federatedIdentityRegistrationEmailMissingMessage=Die E-Mail Adresse ist nicht vorhanden. Bitte verwenden Sie einen anderen Provider um das Benutzerkonto zu erstellen.
federatedIdentityEmailExistsMessage=Es exisitert bereits ein Benutzer mit dieser E-Mail Adresse. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verknüpfen.
federatedIdentityUsernameExistsMessage=Es exisitert bereits ein Benutzer mit diesem Benutzernamen. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verknüpfen.

View file

@ -143,4 +143,3 @@ backToApplication=« Back to Application
missingParameterMessage=Missing parameters\: {0}
clientNotFoundMessage=Client not found.
invalidParameterMessage=Invalid parameter\: {0}
federatedIdentityRegistrationEmailMissingMessage=Email is not provided. Use another provider to create account please.

View file

@ -20,6 +20,7 @@ import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
@ -30,6 +31,7 @@ import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.Time;
import javax.ws.rs.core.Cookie;
@ -43,6 +45,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Iterator;
/**
* Stateless object that manages authentication
@ -366,17 +369,28 @@ public class AuthenticationManager {
Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
UserModel.RequiredAction action = user.getRequiredActions().iterator().next();
accessCode.setRequiredAction(action);
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()).setClientSessionCode(accessCode.getCode()).setUser(user);
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
Iterator<RequiredAction> i = user.getRequiredActions().iterator();
UserModel.RequiredAction action = i.next();
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL) && Validation.isEmpty(user.getEmail())) {
if (i.hasNext())
action = i.next();
else
action = null;
}
return loginFormsProvider
.createResponse(action);
if (action != null) {
accessCode.setRequiredAction(action);
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()).setClientSessionCode(accessCode.getCode())
.setUser(user);
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
}
return loginFormsProvider.createResponse(action);
}
}
if (!isResource) {

View file

@ -171,6 +171,4 @@ public class Messages {
public static final String CLIENT_NOT_FOUND = "clientNotFoundMessage";
public static final String INVALID_PARAMETER = "invalidParameterMessage";
public static final String FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING = "federatedIdentityRegistrationEmailMissingMessage";
}

View file

@ -49,6 +49,7 @@ import org.keycloak.services.managers.EventsManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
import org.keycloak.social.SocialIdentityProvider;
import javax.ws.rs.Consumes;
@ -521,15 +522,10 @@ public class IdentityBrokerService {
}
String username = updatedIdentity.getUsername();
if (this.realmModel.isRegistrationEmailAsUsername()) {
if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isEmpty(updatedIdentity.getEmail())) {
username = updatedIdentity.getEmail();
if (username == null || username.trim().length() == 0) {
fireErrorEvent(Errors.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING);
throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING);
// TODO KEYCLOAK-1053 (ask user to enter email address) should be implemented instead of plain exception as better solution for this case
}
username = username.trim();
} else if (username != null) {
}
if (username != null) {
username = username.trim();
}

View file

@ -32,6 +32,7 @@ import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.representations.IDToken;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.testsuite.OAuthClient;
@ -70,7 +71,6 @@ import static com.thoughtworks.selenium.SeleneseTestBase.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@ -127,7 +127,7 @@ public abstract class AbstractIdentityProviderTest {
public void testSuccessfulAuthentication() {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
assertSuccessfulAuthentication(identityProviderModel, "test-user");
assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com");
}
@Test
@ -135,7 +135,29 @@ public abstract class AbstractIdentityProviderTest {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
identityProviderModel.setUpdateProfileFirstLogin(false);
assertSuccessfulAuthentication(identityProviderModel, "test-user");
assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost");
}
/**
* Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later
*/
@Test
public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() {
getRealm().setVerifyEmail(true);
brokerServerRule.stopSession(this.session, true);
this.session = brokerServerRule.startSession();
try {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
identityProviderModel.setUpdateProfileFirstLogin(false);
UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null);
federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL);
} finally {
getRealm().setVerifyEmail(false);
}
}
@Test
@ -163,7 +185,7 @@ public abstract class AbstractIdentityProviderTest {
assertEquals("test-user@localhost", federatedUser.getUsername());
doAssertFederatedUser(federatedUser, identityProviderModel);
doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost");
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
@ -196,18 +218,38 @@ public abstract class AbstractIdentityProviderTest {
authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail");
// check correct user is created with username from provider as email is not available
RealmModel realm = getRealm();
UserModel federatedUser = session.users().getUserByUsername("test-user-noemail", realm);
assertNull(federatedUser);
UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser);
// assert page is shown with correct error message
assertEquals("Email is not provided. Use another provider to create account please.", this.driver.findElement(By.className("kc-feedback-text")).getText());
doAssertFederatedUserNoEmail(federatedUser);
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
assertEquals(1, federatedIdentities.size());
FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
driver.navigate().to("http://localhost:8081/test-app/logout");
driver.navigate().to("http://localhost:8081/test-app");
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
} finally {
getRealm().setRegistrationEmailAsUsername(false);
}
}
protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
assertEquals("test-user-noemail", federatedUser.getUsername());
assertEquals(null, federatedUser.getEmail());
assertEquals("Test", federatedUser.getFirstName());
assertEquals("User", federatedUser.getLastName());
}
@Test
public void testDisabled() {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
@ -508,7 +550,7 @@ public abstract class AbstractIdentityProviderTest {
protected abstract void doAssertTokenRetrieval(String pageSource);
private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username) {
private UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail) {
authenticateWithIdentityProvider(identityProviderModel, username);
// authenticated and redirected to app
@ -518,7 +560,7 @@ public abstract class AbstractIdentityProviderTest {
assertNotNull(federatedUser);
doAssertFederatedUser(federatedUser, identityProviderModel);
doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail);
RealmModel realm = getRealm();
@ -535,6 +577,7 @@ public abstract class AbstractIdentityProviderTest {
driver.navigate().to("http://localhost:8081/test-app");
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
return federatedUser;
}
private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username) {
@ -593,17 +636,16 @@ public abstract class AbstractIdentityProviderTest {
return this.session.realms().getRealm("realm-with-broker");
}
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail) {
if (identityProviderModel.isUpdateProfileFirstLogin()) {
String userEmail = "new@email.com";
String userFirstName = "New first";
String userLastName = "New last";
assertEquals(userEmail, federatedUser.getEmail());
assertEquals(expectedEmail, federatedUser.getEmail());
assertEquals(userFirstName, federatedUser.getFirstName());
assertEquals(userLastName, federatedUser.getLastName());
} else {
assertEquals("test-user@localhost", federatedUser.getEmail());
assertEquals(expectedEmail, federatedUser.getEmail());
assertEquals("Test", federatedUser.getFirstName());
assertEquals("User", federatedUser.getLastName());
}

View file

@ -45,16 +45,26 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
}
@Override
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail) {
if (identityProviderModel.isUpdateProfileFirstLogin()) {
super.doAssertFederatedUser(federatedUser, identityProviderModel);
super.doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail);
} else {
assertEquals("test-user@localhost", federatedUser.getEmail());
if (expectedEmail == null)
expectedEmail = "";
assertEquals(expectedEmail, federatedUser.getEmail());
assertNull(federatedUser.getFirstName());
assertNull(federatedUser.getLastName());
}
}
@Override
protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
assertEquals("", federatedUser.getUsername());
assertEquals("", federatedUser.getEmail());
assertEquals(null, federatedUser.getFirstName());
assertEquals(null, federatedUser.getLastName());
}
@Override
protected void doAssertTokenRetrieval(String pageSource) {
try {

View file

@ -45,16 +45,26 @@ public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityP
}
@Override
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail) {
if (identityProviderModel.isUpdateProfileFirstLogin()) {
super.doAssertFederatedUser(federatedUser, identityProviderModel);
super.doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail);
} else {
assertEquals("test-user@localhost", federatedUser.getEmail());
if (expectedEmail == null)
expectedEmail = "";
assertEquals(expectedEmail, federatedUser.getEmail());
assertNull(federatedUser.getFirstName());
assertNull(federatedUser.getLastName());
}
}
@Override
protected void doAssertFederatedUserNoEmail(UserModel federatedUser) {
assertEquals("", federatedUser.getUsername());
assertEquals("", federatedUser.getEmail());
assertEquals(null, federatedUser.getFirstName());
assertEquals(null, federatedUser.getLastName());
}
@Override
protected void doAssertTokenRetrieval(String pageSource) {
try {