diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md index 84365eff50..b11455b6af 100644 --- a/testsuite/integration-arquillian/HOW-TO-RUN.md +++ b/testsuite/integration-arquillian/HOW-TO-RUN.md @@ -159,5 +159,52 @@ mvn -f testsuite/integration-arquillian/pom.xml \ ```` +## Social Login +The social login tests require setup of all social networks including an example social user. These details can't be +shared as it would result in the clients and users eventually being blocked. By default these tests are skipped. + +To run the full test you need to configure clients in Google, Facebook, GitHub, Twitter, LinkedIn, Microsoft and +StackOverflow. See the server administration guide for details on how to do that. Further, you also need to create a +sample user that can login to the social network. + +The details should be added to a standard properties file. For some properties you can use shared common properties and +override when needed. Or you can specify these for all providers. All providers require at least clientId and +clientSecret (StackOverflow also requires clientKey). + +An example social.properties file looks like: + + common.username=sampleuser@example.org + common.password=commonpassword + common.profile.firstName=Foo + common.profile.lastName=Bar + common.profile.email=sampleuser@example.org + + google.clientId=asdfasdfasdfasdfsadf + google.clientSecret=zxcvzxcvzxcvzxcv + + facebook.clientId=asdfasdfasdfasdfsadf + facebook.clientSecret=zxcvzxcvzxcvzxcv + facebook.profile.lastName=Test + +In the example above the common username, password and profile are shared for all providers, but Facebook has a +different last name. + +Some providers actively block bots so you need to use a proper browser to test. Either Firefox or Chrome should work. + +To run the tests run: + + mvn -f testsuite/integration-arquillian/pom.xml \ + clean install \ + -Pauth-server-wildfly \ + -Dtest=SocialLoginTest \ + -Dbrowser=chrome \ + -Dsocial.config=/path/to/social.properties +## Different Browsers + +To run with Chrome add `-Dbrowser=chrome`. Depending on the Chrome version you have you may need to download the latest +chromedriver from https://sites.google.com/a/chromium.org/chromedriver/downloads and point to it with +`-Dwebdriver.chrome.driver=/path/to/chromedriver`. + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore index 2df5170f9b..da0f709f5a 100644 Binary files a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore and b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore differ diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java index 9748a9f114..2a3f944c5f 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java @@ -63,6 +63,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { return RealmsResource.accountUrl(UriBuilder.fromUri(getAuthServerRoot())).build("test").toString(); } + public String getPath(String realm) { + return RealmsResource.accountUrl(UriBuilder.fromUri(getAuthServerRoot())).build(realm).toString(); + } + public void updateProfile(String firstName, String lastName, String email) { firstNameInput.clear(); firstNameInput.sendKeys(firstName); @@ -141,6 +145,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { driver.navigate().to(getPath()); } + public void open(String realm) { + driver.navigate().to(getPath(realm)); + } + public void backToApplication() { backToApplicationLink.click(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java index 704060bab3..239cfb9953 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java @@ -41,12 +41,18 @@ public class LoginUpdateProfilePage extends AbstractPage { private WebElement loginErrorMessage; public void update(String firstName, String lastName, String email) { - firstNameInput.clear(); - firstNameInput.sendKeys(firstName); - lastNameInput.clear(); - lastNameInput.sendKeys(lastName); - emailInput.clear(); - emailInput.sendKeys(email); + if (firstName != null) { + firstNameInput.clear(); + firstNameInput.sendKeys(firstName); + } + if (lastName != null) { + lastNameInput.clear(); + lastNameInput.sendKeys(lastName); + } + if (email != null) { + emailInput.clear(); + emailInput.sendKeys(email); + } submitButton.click(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java new file mode 100644 index 0000000000..6ac3970ca6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java @@ -0,0 +1,232 @@ +package org.keycloak.testsuite.broker; + +import org.jboss.arquillian.graphene.Graphene; +import org.jboss.arquillian.graphene.page.Page; +import org.jboss.arquillian.graphene.wait.WebDriverWait; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.keycloak.common.Profile; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.cli.exec.ExecutionException; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.LoginUpdateProfilePage; +import org.keycloak.testsuite.util.IdentityProviderBuilder; +import org.keycloak.testsuite.util.RealmBuilder; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; + +import java.io.FileInputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +/** + * Created by st on 19.01.17. + */ +public class SocialLoginTest extends AbstractKeycloakTest { + + public static final String SOCIAL_CONFIG = "social.config"; + + private static Properties config = new Properties(); + + @Page + public AccountUpdateProfilePage account; + + @Page + public LoginPage loginPage; + + @Page + public LoginUpdateProfilePage updateProfilePage; + + @BeforeClass + public static void loadConfig() throws Exception { + assumeTrue(System.getProperties().containsKey(SOCIAL_CONFIG)); + + config.load(new FileInputStream(System.getProperty(SOCIAL_CONFIG))); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation rep = RealmBuilder.create().name("social").build(); + List idps = new LinkedList<>(); + rep.setIdentityProviders(idps); + + idps.add(buildIdp("google")); + idps.add(buildIdp("facebook")); + idps.add(buildIdp("github")); + idps.add(buildIdp("twitter")); + idps.add(buildIdp("linkedin")); + idps.add(buildIdp("microsoft")); + idps.add(buildIdp("stackoverflow")); + + testRealms.add(rep); + } + + @Test + public void googleLogin() throws InterruptedException { + account.open("social"); + + loginPage.clickSocial("google"); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("Email"))); + + driver.findElement(By.id("Email")).sendKeys(config.getProperty("google.username", config.getProperty("common.username"))); + driver.findElement(By.id("next")).click(); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("Passwd"))); + + driver.findElement(By.id("Passwd")).sendKeys(config.getProperty("google.password", config.getProperty("common.password"))); + driver.findElement(By.id("signIn")).click(); + + Graphene.waitGui().until(ExpectedConditions.elementToBeClickable(By.id("submit_approve_access"))); + + driver.findElement(By.id("submit_approve_access")).click(); + + assertEquals(config.getProperty("google.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("google.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("google.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + @Test + public void faceBookLogin() { + account.open("social"); + + loginPage.clickSocial("facebook"); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("email"))); + driver.findElement(By.id("email")).sendKeys(config.getProperty("facebook.username", config.getProperty("common.username"))); + driver.findElement(By.id("pass")).sendKeys(config.getProperty("facebook.password", config.getProperty("common.password"))); + + driver.findElement(By.id("loginbutton")).click(); + + assertEquals(config.getProperty("facebook.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("facebook.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("facebook.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + @Test + public void githubLogin() { + account.open("social"); + + loginPage.clickSocial("github"); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("login_field"))); + driver.findElement(By.id("login_field")).sendKeys(config.getProperty("github.username", config.getProperty("common.username"))); + driver.findElement(By.id("password")).sendKeys(config.getProperty("github.password", config.getProperty("common.password"))); + + driver.findElement(By.name("commit")).click(); + + assertEquals(config.getProperty("github.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("github.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("github.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + @Test + public void twitterLogin() { + account.open("social"); + + loginPage.clickSocial("twitter"); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("username_or_email"))); + driver.findElement(By.id("username_or_email")).sendKeys(config.getProperty("twitter.username", config.getProperty("common.username"))); + driver.findElement(By.id("password")).sendKeys(config.getProperty("twitter.password", config.getProperty("common.password"))); + + driver.findElement(By.id("allow")).click(); + + assertTrue(updateProfilePage.isCurrent()); + + assertEquals(config.getProperty("twitter.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("twitter.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals("", updateProfilePage.getEmail()); + + updateProfilePage.update(null, null, "keycloakey@gmail.com"); + + assertEquals(config.getProperty("twitter.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("twitter.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("twitter.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + @Test + public void linkedinLogin() { + account.open("social"); + + loginPage.clickSocial("linkedin"); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.id("session_key-oauth2SAuthorizeForm"))); + driver.findElement(By.id("session_key-oauth2SAuthorizeForm")).sendKeys(config.getProperty("linkedin.username", config.getProperty("common.username"))); + driver.findElement(By.id("session_password-oauth2SAuthorizeForm")).sendKeys(config.getProperty("linkedin.password", config.getProperty("common.password"))); + + driver.findElement(By.name("authorize")).click(); + + assertEquals(config.getProperty("linkedin.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("linkedin.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("linkedin.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + @Test + public void microsoftLogin() { + account.open("social"); + + loginPage.clickSocial("microsoft"); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.name("loginfmt"))); + driver.findElement(By.name("loginfmt")).sendKeys(config.getProperty("microsoft.username", config.getProperty("common.username"))); + driver.findElement(By.xpath("//input[@value='Next']")).click(); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.name("passwd"))); + driver.findElement(By.name("passwd")).sendKeys(config.getProperty("microsoft.password", config.getProperty("common.password"))); + driver.findElement(By.xpath("//input[@value='Sign in']")).click(); + + assertEquals(config.getProperty("microsoft.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("microsoft.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("microsoft.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + @Test + public void stackoverflowLogin() { + account.open("social"); + + loginPage.clickSocial("stackoverflow"); + + Graphene.waitModel().until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//a[@title='log in with Stack_Exchange']"))); + driver.findElement(By.xpath("//a[@title='log in with Stack_Exchange']")).click(); + + driver.switchTo().frame(driver.findElement(By.id("affiliate-signin-iframe"))); + + Graphene.waitGui().until(ExpectedConditions.visibilityOfElementLocated(By.name("email"))); + driver.findElement(By.name("email")).sendKeys(config.getProperty("stackoverflow.username", config.getProperty("common.username"))); + driver.findElement(By.name("password")).sendKeys(config.getProperty("stackoverflow.password", config.getProperty("common.password"))); + + driver.findElement(By.xpath("//input[@value='Sign In']")).click(); + + assertEquals(config.getProperty("stackoverflow.profile.firstName", config.getProperty("common.profile.firstName")), updateProfilePage.getFirstName()); + assertEquals(config.getProperty("stackoverflow.profile.lastName", config.getProperty("common.profile.lastName")), updateProfilePage.getLastName()); + assertEquals("", updateProfilePage.getEmail()); + + updateProfilePage.update(null, null, "keycloakey@gmail.com"); + + assertEquals(config.getProperty("stackoverflow.profile.firstName", config.getProperty("common.profile.firstName")), account.getFirstName()); + assertEquals(config.getProperty("stackoverflow.profile.lastName", config.getProperty("common.profile.lastName")), account.getLastName()); + assertEquals(config.getProperty("stackoverflow.profile.email", config.getProperty("common.profile.email")), account.getEmail()); + } + + private IdentityProviderRepresentation buildIdp(String id) { + IdentityProviderRepresentation idp = IdentityProviderBuilder.create().alias(id).providerId(id).build(); + idp.setEnabled(true); + idp.getConfig().put("clientId", config.getProperty(id + ".clientId")); + idp.getConfig().put("clientSecret", config.getProperty(id + ".clientSecret")); + if (id.equals("stackoverflow")) { + idp.getConfig().put("key", config.getProperty(id + ".clientKey")); + } + return idp; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore b/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore index 2df5170f9b..da0f709f5a 100644 Binary files a/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore and b/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore differ