/* * 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.testsuite.account; import org.apache.commons.io.IOUtils; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.jboss.arquillian.drone.api.annotation.Default; import org.jboss.arquillian.graphene.context.GrapheneContext; import org.jboss.arquillian.graphene.page.Page; import org.junit.Before; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.RoleScopeResource; import org.keycloak.models.AccountRoles; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.resources.RealmsResource; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.client.resources.TestApplicationResource; import org.keycloak.testsuite.pages.AccountApplicationsPage; import org.keycloak.testsuite.pages.AccountUpdateProfilePage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.OAuthGrantPage; import org.keycloak.testsuite.runonserver.SerializationUtil; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmRepUtil; import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.WaitUtils; import org.keycloak.util.JsonSerialization; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Platform; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.htmlunit.HtmlUnitDriver; import org.openqa.selenium.remote.DesiredCapabilities; import twitter4j.JSONArray; import twitter4j.JSONObject; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. */ public class ProfileTest extends AbstractTestRealmKeycloakTest { @Override public void configureTestRealm(RealmRepresentation testRealm) { UserRepresentation user = RealmRepUtil.findUser(testRealm, "test-user@localhost"); user.setFirstName("First"); user.setLastName("Last"); user.singleAttribute("key1", "value1"); user.singleAttribute("key2", "value2"); UserRepresentation user2 = UserBuilder.create() .enabled(true) .username("test-user-no-access@localhost") .password("password") .build(); RealmBuilder.edit(testRealm) .accessTokenLifespan(1000) .user(user2); ClientBuilder.edit(RealmRepUtil.findClientByClientId(testRealm, "test-app")) .addWebOrigin("http://localtest.me:8180"); } private RoleRepresentation findViewProfileRole(ClientResource accountApp) { RoleMappingResource scopeMappings = accountApp.getScopeMappings(); RoleScopeResource clientLevelMappings = scopeMappings.clientLevel(accountApp.toRepresentation().getId()); List<RoleRepresentation> accountRoleList = clientLevelMappings.listEffective(); for (RoleRepresentation role : accountRoleList) { if (role.getName().equals(AccountRoles.VIEW_PROFILE)) return role; } return null; } @Before public void addScopeMappings() { String accountClientId = org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_CLIENT_ID; ClientResource accountApp = ApiUtil.findClientByClientId(testRealm(), accountClientId); RoleRepresentation role = findViewProfileRole(accountApp); String accountAppId = accountApp.toRepresentation().getId(); ClientResource app = ApiUtil.findClientByClientId(testRealm(), "test-app"); app.getScopeMappings().clientLevel(accountAppId).add(Collections.singletonList(role)); ClientResource thirdParty = ApiUtil.findClientByClientId(testRealm(), "third-party"); thirdParty.getScopeMappings().clientLevel(accountAppId).add(Collections.singletonList(role)); } @Page protected AccountUpdateProfilePage profilePage; @Page protected AccountApplicationsPage accountApplicationsPage; @Page protected LoginPage loginPage; @Page protected OAuthGrantPage grantPage; @Test public void getProfile() throws Exception { oauth.doLogin("test-user@localhost", "password"); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String token = oauth.doAccessTokenRequest(code, "password").getAccessToken(); HttpResponse response = doGetProfile(token, null); assertEquals(200, response.getStatusLine().getStatusCode()); UserRepresentation profile = JsonSerialization.readValue(IOUtils.toString(response.getEntity().getContent()), UserRepresentation.class); assertEquals("test-user@localhost", profile.getUsername()); assertEquals("test-user@localhost", profile.getEmail()); assertEquals("First", profile.getFirstName()); assertEquals("Last", profile.getLastName()); Map<String, List<String>> attributes = profile.getAttributes(); List<String> attrValue = attributes.get("key1"); assertEquals(1, attrValue.size()); assertEquals("value1", attrValue.get(0)); attrValue = attributes.get("key2"); assertEquals(1, attrValue.size()); assertEquals("value2", attrValue.get(0)); } @Test public void updateProfile() throws Exception { oauth.doLogin("test-user@localhost", "password"); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String token = oauth.doAccessTokenRequest(code, "password").getAccessToken(); UserRepresentation user = new UserRepresentation(); user.setUsername("test-user@localhost"); user.setFirstName("NewFirst"); user.setLastName("NewLast"); user.setEmail("NewEmail@localhost"); HttpResponse response = doUpdateProfile(token, null, JsonSerialization.writeValueAsString(user)); assertEquals(200, response.getStatusLine().getStatusCode()); response = doGetProfile(token, null); UserRepresentation profile = JsonSerialization.readValue(IOUtils.toString(response.getEntity().getContent()), UserRepresentation.class); assertEquals("test-user@localhost", profile.getUsername()); assertEquals("newemail@localhost", profile.getEmail()); assertEquals("NewFirst", profile.getFirstName()); assertEquals("NewLast", profile.getLastName()); // Revert user.setFirstName("First"); user.setLastName("Last"); user.setEmail("test-user@localhost"); doUpdateProfile(token, null, JsonSerialization.writeValueAsString(user)); assertEquals(200, response.getStatusLine().getStatusCode()); } @Test public void getProfileCors() throws Exception { oauth.doLogin("test-user@localhost", "password"); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String token = oauth.doAccessTokenRequest(code, "password").getAccessToken(); driver.navigate().to("http://localtest.me:8180/auth/realms/test/account"); String[] response = doGetProfileJs("http://localtest.me:8180/auth", token); assertEquals("200", response[0]); } // WARN: If it's failing for phantomJS, make sure to enable CORS by using: // -Dphantomjs.cli.args="--ignore-ssl-errors=true --web-security=true" @Test public void getProfileCorsInvalidOrigin() throws Exception { oauth.doLogin("test-user@localhost", "password"); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String token = oauth.doAccessTokenRequest(code, "password").getAccessToken(); String[] response = null; try { response = doGetProfileJs("http://invalid.localtest.me:8180/auth", token); } catch (WebDriverException ex) { // Expected } // Some webDrivers throw exception (htmlUnit) , some just doesn't return anything. if (response != null && response.length > 0 && response[0].equals("200")) { fail("Not expected to retrieve response. Make sure CORS are enabled for your browser!"); } } @Test public void getProfileCookieAuth() throws Exception { profilePage.open(); loginPage.login("test-user@localhost", "password"); String[] response = doGetProfileJs(OAuthClient.AUTH_SERVER_ROOT, null); assertEquals("200", response[0]); JSONObject profile = new JSONObject(response[1]); assertEquals("test-user@localhost", profile.getString("username")); } @Test public void getProfileNoAuth() throws Exception { HttpResponse response = doGetProfile(null, null); assertEquals(403, response.getStatusLine().getStatusCode()); } @Test public void getProfileNoAccess() throws Exception { oauth.doLogin("test-user-no-access@localhost", "password"); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String token = oauth.doAccessTokenRequest(code, "password").getAccessToken(); HttpResponse response = doGetProfile(token, null); assertEquals(403, response.getStatusLine().getStatusCode()); } @Test public void getProfileOAuthClient() throws Exception { oauth.clientId("third-party"); oauth.doLoginGrant("test-user@localhost", "password"); grantPage.accept(); String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password").getAccessToken(); HttpResponse response = doGetProfile(token, null); assertEquals(200, response.getStatusLine().getStatusCode()); JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent())); assertEquals("test-user@localhost", profile.getString("username")); accountApplicationsPage.open(); accountApplicationsPage.revokeGrant("third-party"); } @Test public void getProfileOAuthClientNoScope() throws Exception { oauth.clientId("third-party"); oauth.doLoginGrant("test-user@localhost", "password"); String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password").getAccessToken(); HttpResponse response = doGetProfile(token, null); assertEquals(403, response.getStatusLine().getStatusCode()); } private URI getAccountURI() { return RealmsResource.accountUrl(UriBuilder.fromUri(oauth.AUTH_SERVER_ROOT)).build(oauth.getRealm()); } private HttpResponse doGetProfile(String token, String origin) throws IOException { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(UriBuilder.fromUri(getAccountURI()).build()); if (token != null) { get.setHeader(HttpHeaders.AUTHORIZATION, "bearer " + token); } if (origin != null) { get.setHeader("Origin", origin); } get.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); return client.execute(get); } private HttpResponse doUpdateProfile(String token, String origin, String value) throws IOException { HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost(UriBuilder.fromUri(getAccountURI()).build()); if (token != null) { post.setHeader(HttpHeaders.AUTHORIZATION, "bearer " + token); } if (origin != null) { post.setHeader("Origin", origin); } post.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); post.setEntity(new StringEntity(value)); return client.execute(post); } private String[] doGetProfileJs(String authServerRoot, String token) { UriBuilder uriBuilder = UriBuilder.fromUri(authServerRoot) .path(TestApplicationResource.class) .path(TestApplicationResource.class, "getAccountProfile") .queryParam("account-uri", getAccountURI().toString()); if (token != null) { uriBuilder.queryParam("token", token); // Remove Keycloak cookies. Some browsers send cookies even in preflight requests driver.navigate().to(OAuthClient.AUTH_SERVER_ROOT + "/realms/test/account"); driver.manage().deleteAllCookies(); } String accountProfileUri = uriBuilder.build().toString(); log.info("Retrieve profile with URI: " + accountProfileUri); driver.navigate().to(accountProfileUri); WaitUtils.waitUntilElement(By.id("innerOutput")); String response = driver.findElement(By.id("innerOutput")).getText(); return response.split("///"); } private WebDriver getHtmlUnitDriver() { DesiredCapabilities cap = new DesiredCapabilities(); cap.setPlatform(Platform.ANY); cap.setJavascriptEnabled(true); cap.setVersion("chrome"); cap.setBrowserName("htmlunit"); HtmlUnitDriver driver = new HtmlUnitDriver(cap); return driver; } }