KEYCLOAK-5234 (#4585)
This commit is contained in:
1 parent
20d0fa1b4e
commit
9b75b603e3
16 files changed
+320
-104
No files matched your search
+3
-17
@@ -46,7 +46,6 @@ public class AccountFederatedIdentityBean {
|
|||||||
|
|
||||||
public AccountFederatedIdentityBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
|
public AccountFederatedIdentityBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
URI accountIdentityUpdateUri = Urls.accountFederatedIdentityUpdate(baseUri, realm.getName());
|
|
||||||
|
|
||||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||||
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
|
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
|
||||||
@@ -63,15 +62,8 @@ public class AccountFederatedIdentityBean {
|
|||||||
availableIdentities++;
|
availableIdentities++;
|
||||||
}
|
}
|
||||||
|
|
||||||
String action = identity != null ? "remove" : "add";
|
|
||||||
String actionUrl = UriBuilder.fromUri(accountIdentityUpdateUri)
|
|
||||||
.queryParam("action", action)
|
|
||||||
.queryParam("provider_id", providerId)
|
|
||||||
.queryParam("stateChecker", stateChecker)
|
|
||||||
.build().toString();
|
|
||||||
|
|
||||||
String displayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, provider);
|
String displayName = KeycloakModelUtils.getIdentityProviderDisplayName(session, provider);
|
||||||
FederatedIdentityEntry entry = new FederatedIdentityEntry(identity, displayName, provider.getAlias(), provider.getAlias(), actionUrl,
|
FederatedIdentityEntry entry = new FederatedIdentityEntry(identity, displayName, provider.getAlias(), provider.getAlias(),
|
||||||
provider.getConfig() != null ? provider.getConfig().get("guiOrder") : null);
|
provider.getConfig() != null ? provider.getConfig().get("guiOrder") : null);
|
||||||
orderedSet.add(entry);
|
orderedSet.add(entry);
|
||||||
}
|
}
|
||||||
@@ -105,17 +97,15 @@ public class AccountFederatedIdentityBean {
|
|||||||
private FederatedIdentityModel federatedIdentityModel;
|
private FederatedIdentityModel federatedIdentityModel;
|
||||||
private final String providerId;
|
private final String providerId;
|
||||||
private final String providerName;
|
private final String providerName;
|
||||||
private final String actionUrl;
|
|
||||||
private final String guiOrder;
|
private final String guiOrder;
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
public FederatedIdentityEntry(FederatedIdentityModel federatedIdentityModel, String displayName, String providerId,
|
public FederatedIdentityEntry(FederatedIdentityModel federatedIdentityModel, String displayName, String providerId,
|
||||||
String providerName, String actionUrl, String guiOrder) {
|
String providerName, String guiOrder) {
|
||||||
this.federatedIdentityModel = federatedIdentityModel;
|
this.federatedIdentityModel = federatedIdentityModel;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.providerId = providerId;
|
this.providerId = providerId;
|
||||||
this.providerName = providerName;
|
this.providerName = providerName;
|
||||||
this.actionUrl = actionUrl;
|
|
||||||
this.guiOrder = guiOrder;
|
this.guiOrder = guiOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,10 +129,6 @@ public class AccountFederatedIdentityBean {
|
|||||||
return federatedIdentityModel != null;
|
return federatedIdentityModel != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getActionUrl() {
|
|
||||||
return actionUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGuiOrder() {
|
public String getGuiOrder() {
|
||||||
return guiOrder;
|
return guiOrder;
|
||||||
}
|
}
|
||||||
@@ -186,4 +172,4 @@ public class AccountFederatedIdentityBean {
|
|||||||
return 10000;
|
return 10000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,6 @@ public class UrlBean {
|
|||||||
private URI baseURI;
|
private URI baseURI;
|
||||||
private URI baseQueryURI;
|
private URI baseQueryURI;
|
||||||
private URI currentURI;
|
private URI currentURI;
|
||||||
private String stateChecker;
|
|
||||||
|
|
||||||
public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI baseQueryURI, URI currentURI, String stateChecker) {
|
public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI baseQueryURI, URI currentURI, String stateChecker) {
|
||||||
this.realm = realm.getName();
|
this.realm = realm.getName();
|
||||||
@@ -41,7 +40,6 @@ public class UrlBean {
|
|||||||
this.baseURI = baseURI;
|
this.baseURI = baseURI;
|
||||||
this.baseQueryURI = baseQueryURI;
|
this.baseQueryURI = baseQueryURI;
|
||||||
this.currentURI = currentURI;
|
this.currentURI = currentURI;
|
||||||
this.stateChecker = stateChecker;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApplicationsUrl() {
|
public String getApplicationsUrl() {
|
||||||
@@ -73,7 +71,7 @@ public class UrlBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSessionsLogoutUrl() {
|
public String getSessionsLogoutUrl() {
|
||||||
return Urls.accountSessionsLogoutPage(baseQueryURI, realm, stateChecker).toString();
|
return Urls.accountSessionsLogoutPage(baseQueryURI, realm).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRevokeClientUrl() {
|
public String getRevokeClientUrl() {
|
||||||
@@ -81,7 +79,7 @@ public class UrlBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getTotpRemoveUrl() {
|
public String getTotpRemoveUrl() {
|
||||||
return Urls.accountTotpRemove(baseQueryURI, realm, stateChecker).toString();
|
return Urls.accountTotpRemove(baseQueryURI, realm).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLogoutUrl() {
|
public String getLogoutUrl() {
|
||||||
|
|||||||
@@ -126,9 +126,8 @@ public class Urls {
|
|||||||
return accountBase(baseUri).path(AccountFormService.class, "totpPage").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "totpPage").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountTotpRemove(URI baseUri, String realmName, String stateChecker) {
|
public static URI accountTotpRemove(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountFormService.class, "processTotpRemove")
|
return accountBase(baseUri).path(AccountFormService.class, "processTotpRemove")
|
||||||
.queryParam("stateChecker", stateChecker)
|
|
||||||
.build(realmName);
|
.build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,9 +139,8 @@ public class Urls {
|
|||||||
return accountBase(baseUri).path(AccountFormService.class, "sessionsPage").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "sessionsPage").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountSessionsLogoutPage(URI baseUri, String realmName, String stateChecker) {
|
public static URI accountSessionsLogoutPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountFormService.class, "processSessionsLogout")
|
return accountBase(baseUri).path(AccountFormService.class, "processSessionsLogout")
|
||||||
.queryParam("stateChecker", stateChecker)
|
|
||||||
.build(realmName);
|
.build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -572,6 +572,11 @@ public class AuthenticationManager {
|
|||||||
return uri.getRawPath();
|
return uri.getRawPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAccountCookiePath(RealmModel realm, UriInfo uriInfo) {
|
||||||
|
URI uri = RealmsResource.accountUrl(uriInfo.getBaseUriBuilder()).build(realm.getName());
|
||||||
|
return uri.getRawPath();
|
||||||
|
}
|
||||||
|
|
||||||
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection) {
|
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection) {
|
||||||
logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path);
|
logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);;
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);;
|
||||||
|
|||||||
+27
-21
@@ -24,14 +24,12 @@ import org.keycloak.OAuth2Constants;
|
|||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import org.keycloak.common.util.UriUtils;
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.services.ForbiddenException;
|
import org.keycloak.services.ForbiddenException;
|
||||||
import org.keycloak.services.managers.AppAuthManager;
|
|
||||||
import org.keycloak.services.managers.Auth;
|
import org.keycloak.services.managers.Auth;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
@@ -130,14 +128,20 @@ public abstract class AbstractSecuredLocalService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void updateCsrfChecks() {
|
protected void updateCsrfChecks() {
|
||||||
Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
|
stateChecker = getStateChecker();
|
||||||
if (cookie != null) {
|
if (stateChecker == null) {
|
||||||
stateChecker = cookie.getValue();
|
|
||||||
} else {
|
|
||||||
stateChecker = Base64Url.encode(KeycloakModelUtils.generateSecret());
|
stateChecker = Base64Url.encode(KeycloakModelUtils.generateSecret());
|
||||||
String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(auth.getSession().getId());
|
||||||
|
sb.append("/");
|
||||||
|
sb.append(stateChecker);
|
||||||
|
|
||||||
|
String sessionCookieValue = sb.toString();
|
||||||
|
|
||||||
|
String cookiePath = AuthenticationManager.getAccountCookiePath(realm, uriInfo);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(clientConnection);
|
boolean secureOnly = realm.getSslRequired().isRequired(clientConnection);
|
||||||
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);
|
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, sessionCookieValue, cookiePath, null, null, -1, secureOnly, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,25 +153,27 @@ public abstract class AbstractSecuredLocalService {
|
|||||||
* @param formData
|
* @param formData
|
||||||
*/
|
*/
|
||||||
protected void csrfCheck(final MultivaluedMap<String, String> formData) {
|
protected void csrfCheck(final MultivaluedMap<String, String> formData) {
|
||||||
if (!auth.isCookieAuthenticated()) return;
|
|
||||||
String stateChecker = formData.getFirst("stateChecker");
|
String stateChecker = formData.getFirst("stateChecker");
|
||||||
if (!this.stateChecker.equals(stateChecker)) {
|
if (stateChecker == null || !stateChecker.equals(getStateChecker())) {
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected String getStateChecker() {
|
||||||
* Check to see if form post has sessionId hidden field and match it against the session id.
|
Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
|
||||||
*
|
if (cookie != null) {
|
||||||
*/
|
stateChecker = cookie.getValue();
|
||||||
protected void csrfCheck(String stateChecker) {
|
String[] s = stateChecker.split("/");
|
||||||
if (!auth.isCookieAuthenticated()) return;
|
if (s.length == 2) {
|
||||||
if (auth.getSession() == null) return;
|
String sessionId = s[0];
|
||||||
if (!this.stateChecker.equals(stateChecker)) {
|
String stateChecker = s[1];
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (auth.getSession().getId().equals(sessionId)) {
|
||||||
|
return stateChecker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract URI getBaseRedirectUri();
|
protected abstract URI getBaseRedirectUri();
|
||||||
|
|||||||
+15
-12
@@ -44,6 +44,7 @@ import org.keycloak.models.UserModel;
|
|||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.services.ForbiddenException;
|
import org.keycloak.services.ForbiddenException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
@@ -343,15 +344,15 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Path("totp-remove")
|
@Path("totp-remove")
|
||||||
@GET
|
@POST
|
||||||
public Response processTotpRemove(@QueryParam("stateChecker") String stateChecker) {
|
public Response processTotpRemove(final MultivaluedMap<String, String> formData) {
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
return login("totp");
|
return login("totp");
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(formData);
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
|
session.userCredentialManager().disableCredentialType(realm, user, CredentialModel.OTP);
|
||||||
@@ -364,14 +365,14 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
|||||||
|
|
||||||
|
|
||||||
@Path("sessions-logout")
|
@Path("sessions-logout")
|
||||||
@GET
|
@POST
|
||||||
public Response processSessionsLogout(@QueryParam("stateChecker") String stateChecker) {
|
public Response processSessionsLogout(final MultivaluedMap<String, String> formData) {
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
return login("sessions");
|
return login("sessions");
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(formData);
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
@@ -588,19 +589,21 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
|||||||
return account.setPasswordSet(true).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createResponse(AccountPages.PASSWORD);
|
return account.setPasswordSet(true).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("federated-identity-update")
|
@Path("identity")
|
||||||
@GET
|
@POST
|
||||||
public Response processFederatedIdentityUpdate(@QueryParam("action") String action,
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
@QueryParam("provider_id") String providerId,
|
public Response processFederatedIdentityUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
@QueryParam("stateChecker") String stateChecker) {
|
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
return login("identity");
|
return login("identity");
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(formData);
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
|
String action = formData.getFirst("action");
|
||||||
|
String providerId = formData.getFirst("providerId");
|
||||||
|
|
||||||
if (Validation.isEmpty(providerId)) {
|
if (Validation.isEmpty(providerId)) {
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
return account.setError(Messages.MISSING_IDENTITY_PROVIDER).createResponse(AccountPages.FEDERATED_IDENTITY);
|
return account.setError(Messages.MISSING_IDENTITY_PROVIDER).createResponse(AccountPages.FEDERATED_IDENTITY);
|
||||||
|
|||||||
+62
-13
@@ -22,6 +22,9 @@ import org.openqa.selenium.By;
|
|||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@@ -50,26 +53,72 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage {
|
|||||||
public boolean isCurrent() {
|
public boolean isCurrent() {
|
||||||
return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Federated Identities");
|
return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Federated Identities");
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebElement findAddProviderButton(String alias) {
|
public List<FederatedIdentity> getIdentities() {
|
||||||
return driver.findElement(By.id("add-" + alias));
|
List<FederatedIdentity> identities = new LinkedList<>();
|
||||||
}
|
WebElement identitiesElement = driver.findElement(By.id("federated-identities"));
|
||||||
|
for (WebElement i : identitiesElement.findElements(By.className("row"))) {
|
||||||
public WebElement findRemoveProviderButton(String alias) {
|
|
||||||
return driver.findElement(By.id("remove-" + alias));
|
String providerId = i.findElement(By.tagName("label")).getText();
|
||||||
|
String subject = i.findElement(By.tagName("input")).getAttribute("value");
|
||||||
|
WebElement button = i.findElement(By.tagName("button"));
|
||||||
|
|
||||||
|
identities.add(new FederatedIdentity(providerId, subject, button));
|
||||||
|
}
|
||||||
|
return identities;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickAddProvider(String alias) {
|
public WebElement findAddProvider(String providerId) {
|
||||||
WebElement addButton = findAddProviderButton(alias);
|
return driver.findElement(By.id("add-link-" + providerId));
|
||||||
addButton.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickRemoveProvider(String alias) {
|
public void clickAddProvider(String providerId) {
|
||||||
WebElement addButton = findRemoveProviderButton(alias);
|
findAddProvider(providerId).click();
|
||||||
addButton.click();
|
}
|
||||||
|
|
||||||
|
public void clickRemoveProvider(String providerId) {
|
||||||
|
driver.findElement(By.id("remove-link-" + providerId)).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getError() {
|
public String getError() {
|
||||||
return errorMessage.getText();
|
return errorMessage.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class FederatedIdentity {
|
||||||
|
|
||||||
|
private String providerId;
|
||||||
|
private String subject;
|
||||||
|
private WebElement action;
|
||||||
|
|
||||||
|
public FederatedIdentity(String providerId, String subject, WebElement action) {
|
||||||
|
this.providerId = providerId;
|
||||||
|
this.subject = subject;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProvider() {
|
||||||
|
return providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderId(String providerId) {
|
||||||
|
this.providerId = providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebElement getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(WebElement action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* 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.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.broker.AbstractBaseBrokerTest;
|
||||||
|
import org.keycloak.testsuite.broker.BrokerConfiguration;
|
||||||
|
import org.keycloak.testsuite.broker.KcOidcBrokerConfiguration;
|
||||||
|
import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
|
||||||
|
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class AccountBrokerTest extends AbstractBaseBrokerTest {
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected AccountFederatedIdentityPage identityPage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return KcOidcBrokerConfiguration.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createUser() {
|
||||||
|
log.debug("creating user for realm " + bc.providerRealmName());
|
||||||
|
|
||||||
|
UserRepresentation user = new UserRepresentation();
|
||||||
|
user.setUsername(bc.getUserLogin());
|
||||||
|
user.setEmail(bc.getUserEmail());
|
||||||
|
user.setEmailVerified(true);
|
||||||
|
user.setEnabled(true);
|
||||||
|
|
||||||
|
RealmResource realmResource = adminClient.realm(bc.providerRealmName());
|
||||||
|
userId = createUserWithAdminClient(realmResource, user);
|
||||||
|
|
||||||
|
resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void addIdentityProviderToProviderRealm() {
|
||||||
|
log.debug("adding identity provider to realm " + bc.consumerRealmName());
|
||||||
|
|
||||||
|
RealmResource realm = adminClient.realm(bc.consumerRealmName());
|
||||||
|
realm.identityProviders().create(bc.setUpIdentityProvider(suiteContext)).close();
|
||||||
|
realm.identityProviders().get(bc.getIDPAlias());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void addClients() {
|
||||||
|
List<ClientRepresentation> clients = bc.createProviderClients(suiteContext);
|
||||||
|
if (clients != null) {
|
||||||
|
RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
|
||||||
|
for (ClientRepresentation client : clients) {
|
||||||
|
log.debug("adding client " + client.getName() + " to realm " + bc.providerRealmName());
|
||||||
|
|
||||||
|
providerRealm.clients().create(client).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clients = bc.createConsumerClients(suiteContext);
|
||||||
|
if (clients != null) {
|
||||||
|
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
|
||||||
|
for (ClientRepresentation client : clients) {
|
||||||
|
log.debug("adding client " + client.getName() + " to realm " + bc.consumerRealmName());
|
||||||
|
|
||||||
|
consumerRealm.clients().create(client).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
Response response = adminClient.realm(KcOidcBrokerConfiguration.INSTANCE.consumerRealmName()).users().create(UserBuilder.create().username("accountbrokertest").build());
|
||||||
|
String userId = ApiUtil.getCreatedId(response);
|
||||||
|
ApiUtil.resetUserPassword(adminClient.realm(KcOidcBrokerConfiguration.INSTANCE.consumerRealmName()).users().get(userId), "password", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void add() {
|
||||||
|
identityPage.realm(KcOidcBrokerConfiguration.INSTANCE.consumerRealmName());
|
||||||
|
identityPage.open();
|
||||||
|
loginPage.login("accountbrokertest", "password");
|
||||||
|
Assert.assertTrue(identityPage.isCurrent());
|
||||||
|
|
||||||
|
List<AccountFederatedIdentityPage.FederatedIdentity> identities = identityPage.getIdentities();
|
||||||
|
Assert.assertEquals(1, identities.size());
|
||||||
|
|
||||||
|
Assert.assertEquals("kc-oidc-idp", identities.get(0).getProvider());
|
||||||
|
Assert.assertEquals("", identities.get(0).getSubject());
|
||||||
|
Assert.assertEquals("add-link-kc-oidc-idp", identities.get(0).getAction().getAttribute("id"));
|
||||||
|
|
||||||
|
identities.get(0).getAction().click();
|
||||||
|
|
||||||
|
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
|
||||||
|
|
||||||
|
Assert.assertTrue(identityPage.isCurrent());
|
||||||
|
|
||||||
|
identities = identityPage.getIdentities();
|
||||||
|
Assert.assertEquals(1, identities.size());
|
||||||
|
|
||||||
|
Assert.assertEquals("kc-oidc-idp", identities.get(0).getProvider());
|
||||||
|
Assert.assertEquals("user@localhost.com", identities.get(0).getSubject());
|
||||||
|
Assert.assertEquals("remove-link-kc-oidc-idp", identities.get(0).getAction().getAttribute("id"));
|
||||||
|
|
||||||
|
identities.get(0).getAction().click();
|
||||||
|
|
||||||
|
Assert.assertTrue(identityPage.isCurrent());
|
||||||
|
|
||||||
|
identities = identityPage.getIdentities();
|
||||||
|
|
||||||
|
Assert.assertEquals("kc-oidc-idp", identities.get(0).getProvider());
|
||||||
|
Assert.assertEquals("", identities.get(0).getSubject());
|
||||||
|
Assert.assertEquals("add-link-kc-oidc-idp", identities.get(0).getAction().getAttribute("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -993,7 +993,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
|||||||
public void testIdentityProviderHiddenOnLoginPageIsVisbleInAccount(){
|
public void testIdentityProviderHiddenOnLoginPageIsVisbleInAccount(){
|
||||||
federatedIdentityPage.open();
|
federatedIdentityPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
Assert.assertNotNull(federatedIdentityPage.findAddProviderButton("myhiddenoidc"));
|
Assert.assertNotNull(federatedIdentityPage.findAddProvider("myhiddenoidc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+3
-3
@@ -148,7 +148,7 @@ public class AccountLinkTest extends AbstractKeycloakTest {
|
|||||||
|
|
||||||
// Assert identity linked in account management
|
// Assert identity linked in account management
|
||||||
assertTrue(accountFederatedIdentityPage.isCurrent());
|
assertTrue(accountFederatedIdentityPage.isCurrent());
|
||||||
assertTrue(driver.getPageSource().contains("id=\"remove-" + PARENT_IDP + "\""));
|
assertTrue(driver.getPageSource().contains("id=\"remove-link-" + PARENT_IDP + "\""));
|
||||||
|
|
||||||
// Logout from account management
|
// Logout from account management
|
||||||
accountFederatedIdentityPage.logout();
|
accountFederatedIdentityPage.logout();
|
||||||
@@ -161,11 +161,11 @@ public class AccountLinkTest extends AbstractKeycloakTest {
|
|||||||
System.out.println("--------------------------------");
|
System.out.println("--------------------------------");
|
||||||
System.out.println(driver.getPageSource());
|
System.out.println(driver.getPageSource());
|
||||||
assertTrue(accountFederatedIdentityPage.isCurrent());
|
assertTrue(accountFederatedIdentityPage.isCurrent());
|
||||||
assertTrue(driver.getPageSource().contains("id=\"remove-" + PARENT_IDP + "\""));
|
assertTrue(driver.getPageSource().contains("id=\"remove-link-" + PARENT_IDP + "\""));
|
||||||
|
|
||||||
// Unlink my "test-user"
|
// Unlink my "test-user"
|
||||||
accountFederatedIdentityPage.clickRemoveProvider(PARENT_IDP);
|
accountFederatedIdentityPage.clickRemoveProvider(PARENT_IDP);
|
||||||
assertTrue(driver.getPageSource().contains("id=\"add-" + PARENT_IDP + "\""));
|
assertTrue(driver.getPageSource().contains("id=\"add-link-" + PARENT_IDP + "\""));
|
||||||
|
|
||||||
|
|
||||||
// Logout from account management
|
// Logout from account management
|
||||||
|
|||||||
+3
-3
@@ -420,7 +420,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
|
|||||||
|
|
||||||
// Assert identity linked in account management
|
// Assert identity linked in account management
|
||||||
assertTrue(accountFederatedIdentityPage.isCurrent());
|
assertTrue(accountFederatedIdentityPage.isCurrent());
|
||||||
assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
|
assertTrue(driver.getPageSource().contains("id=\"remove-link-" + identityProviderModel.getAlias() + "\""));
|
||||||
|
|
||||||
// Revoke grant in account mgmt
|
// Revoke grant in account mgmt
|
||||||
revokeGrant();
|
revokeGrant();
|
||||||
@@ -434,11 +434,11 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
|
|||||||
this.loginPage.login("test-user", "password");
|
this.loginPage.login("test-user", "password");
|
||||||
doAfterProviderAuthentication();
|
doAfterProviderAuthentication();
|
||||||
assertTrue(accountFederatedIdentityPage.isCurrent());
|
assertTrue(accountFederatedIdentityPage.isCurrent());
|
||||||
assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
|
assertTrue(driver.getPageSource().contains("id=\"remove-link-" + identityProviderModel.getAlias() + "\""));
|
||||||
|
|
||||||
// Unlink my "test-user"
|
// Unlink my "test-user"
|
||||||
accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
|
accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
|
||||||
assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
|
assertTrue(driver.getPageSource().contains("id=\"add-link-" + identityProviderModel.getAlias() + "\""));
|
||||||
|
|
||||||
// Revoke grant in account mgmt
|
// Revoke grant in account mgmt
|
||||||
revokeGrant();
|
revokeGrant();
|
||||||
|
|||||||
+2
-2
@@ -55,11 +55,11 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clickAddProvider(String providerId) {
|
public void clickAddProvider(String providerId) {
|
||||||
driver.findElement(By.id("add-" + providerId)).click();
|
driver.findElement(By.id("add-link-" + providerId)).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clickRemoveProvider(String providerId) {
|
public void clickRemoveProvider(String providerId) {
|
||||||
driver.findElement(By.id("remove-" + providerId)).click();
|
driver.findElement(By.id("remove-link-" + providerId)).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getError() {
|
public String getError() {
|
||||||
|
|||||||
@@ -7,26 +7,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="${url.passwordUrl}" class="form-horizontal" method="post">
|
<div id="federated-identities">
|
||||||
<#list federatedIdentity.identities as identity>
|
<#list federatedIdentity.identities as identity>
|
||||||
<div class="form-group">
|
<div class="row margin-bottom">
|
||||||
<div class="col-sm-2 col-md-2">
|
<div class="col-sm-2 col-md-2">
|
||||||
<label for="${identity.providerId!}" class="control-label">${identity.displayName!}</label>
|
<label for="${identity.providerId!}" class="control-label">${identity.displayName!}</label>
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-md-5">
|
|
||||||
<input disabled="true" class="form-control" value="${identity.userName!}">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-md-5">
|
|
||||||
<#if identity.connected>
|
|
||||||
<#if federatedIdentity.removeLinkPossible>
|
|
||||||
<a href="${identity.actionUrl}" type="submit" id="remove-${identity.providerId!}" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}">${msg("doRemove")}</a>
|
|
||||||
</#if>
|
|
||||||
<#else>
|
|
||||||
<a href="${identity.actionUrl}" type="submit" id="add-${identity.providerId!}" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}">${msg("doAdd")}</a>
|
|
||||||
</#if>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</#list>
|
<div class="col-sm-5 col-md-5">
|
||||||
</form>
|
<input disabled="true" class="form-control" value="${identity.userName!}">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5 col-md-5">
|
||||||
|
<#if identity.connected>
|
||||||
|
<#if federatedIdentity.removeLinkPossible>
|
||||||
|
<form action="${url.socialUrl}" method="post" class="form-inline">
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
|
||||||
|
<input type="hidden" id="action" name="action" value="remove">
|
||||||
|
<input type="hidden" id="providerId" name="providerId" value="${identity.providerId!}">
|
||||||
|
<button id="remove-link-${identity.providerId!}" class="btn btn-default">${msg("doRemove")}</button>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
<#else>
|
||||||
|
<form action="${url.socialUrl}" method="post" class="form-inline">
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
|
||||||
|
<input type="hidden" id="action" name="action" value="add">
|
||||||
|
<input type="hidden" id="providerId" name="providerId" value="${identity.providerId!}">
|
||||||
|
<button id="add-link-${identity.providerId!}" class="btn btn-default">${msg("doAdd")}</button>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#list>
|
||||||
|
</div>
|
||||||
|
|
||||||
</@layout.mainLayout>
|
</@layout.mainLayout>
|
||||||
@@ -36,6 +36,9 @@
|
|||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${msg("doLogOutAllSessions")}</a>
|
<form action="${url.sessionsLogoutUrl}" method="post">
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
|
||||||
|
<button id="logout-all-sessions" class="btn btn-default">${msg("doLogOutAllSessions")}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
</@layout.mainLayout>
|
</@layout.mainLayout>
|
||||||
@@ -14,7 +14,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="provider">${msg("mobile")}</td>
|
<td class="provider">${msg("mobile")}</td>
|
||||||
<td class="action">
|
<td class="action">
|
||||||
<a id="remove-mobile" href="${url.totpRemoveUrl}"><i class="pficon pficon-delete"></i></a>
|
<form action="${url.totpRemoveUrl}" method="post" class="form-inline">
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker?html}">
|
||||||
|
<button id="remove-mobile" class="btn btn-default"><i class="pficon pficon-delete"></i></button>
|
||||||
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ header .navbar {
|
|||||||
padding: 0 30px;
|
padding: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.margin-bottom {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
|
|
||||||
.bs-sidebar {
|
.bs-sidebar {
|
||||||
@@ -262,4 +266,4 @@ hr + .form-horizontal {
|
|||||||
}
|
}
|
||||||
.kc-dropdown:hover ul{
|
.kc-dropdown:hover ul{
|
||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user