KEYCLOAK-943 Started account rest service. Profile and sessions completed. (#4439)
This commit is contained in:
parent
463661b051
commit
dcfa4aca8c
39 changed files with 1331 additions and 650 deletions
|
@ -0,0 +1,25 @@
|
||||||
|
package org.keycloak.representations.account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by st on 29/03/17.
|
||||||
|
*/
|
||||||
|
public class ClientRepresentation {
|
||||||
|
private String clientId;
|
||||||
|
private String clientName;
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientName() {
|
||||||
|
return clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientName(String clientName) {
|
||||||
|
this.clientName = clientName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.keycloak.representations.account;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by st on 29/03/17.
|
||||||
|
*/
|
||||||
|
public class SessionRepresentation {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String ipAddress;
|
||||||
|
private int started;
|
||||||
|
private int lastAccess;
|
||||||
|
private int expires;
|
||||||
|
private List<ClientRepresentation> clients;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(String ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStarted() {
|
||||||
|
return started;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStarted(int started) {
|
||||||
|
this.started = started;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastAccess() {
|
||||||
|
return lastAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastAccess(int lastAccess) {
|
||||||
|
this.lastAccess = lastAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExpires() {
|
||||||
|
return expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpires(int expires) {
|
||||||
|
this.expires = expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClientRepresentation> getClients() {
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClients(List<ClientRepresentation> clients) {
|
||||||
|
this.clients = clients;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* 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.representations.account;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import org.keycloak.json.StringListMapDeserializer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class UserRepresentation {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String username;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private String email;
|
||||||
|
private boolean emailVerified;
|
||||||
|
|
||||||
|
@JsonDeserialize(using = StringListMapDeserializer.class)
|
||||||
|
private Map<String, List<String>> attributes;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmailVerified() {
|
||||||
|
return emailVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailVerified(boolean emailVerified) {
|
||||||
|
this.emailVerified = emailVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Map<String, List<String>> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -80,7 +80,7 @@ public class AccountFederatedIdentityBean {
|
||||||
this.identities = new LinkedList<FederatedIdentityEntry>(orderedSet);
|
this.identities = new LinkedList<FederatedIdentityEntry>(orderedSet);
|
||||||
|
|
||||||
// Removing last social provider is not possible if you don't have other possibility to authenticate
|
// Removing last social provider is not possible if you don't have other possibility to authenticate
|
||||||
this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(session, realm, user);
|
this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountFormService.isPasswordSet(session, realm, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FederatedIdentityModel getIdentity(Set<FederatedIdentityModel> identities, String providerId) {
|
private FederatedIdentityModel getIdentity(Set<FederatedIdentityModel> identities, String providerId) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import org.keycloak.common.Version;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.services.resources.IdentityBrokerService;
|
import org.keycloak.services.resources.IdentityBrokerService;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
@ -41,7 +41,7 @@ public class Urls {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountApplicationsPage(URI baseUri, String realmName) {
|
public static URI accountApplicationsPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "applicationsPage").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "applicationsPage").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder accountBase(URI baseUri) {
|
public static UriBuilder accountBase(URI baseUri) {
|
||||||
|
@ -53,19 +53,19 @@ public class Urls {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder accountPageBuilder(URI baseUri) {
|
public static UriBuilder accountPageBuilder(URI baseUri) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "accountPage");
|
return accountBase(baseUri).path(AccountFormService.class, "accountPage");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountPasswordPage(URI baseUri, String realmName) {
|
public static URI accountPasswordPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "passwordPage").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "passwordPage").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountFederatedIdentityPage(URI baseUri, String realmName) {
|
public static URI accountFederatedIdentityPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "federatedIdentityPage").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "federatedIdentityPage").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountFederatedIdentityUpdate(URI baseUri, String realmName) {
|
public static URI accountFederatedIdentityUpdate(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "processFederatedIdentityUpdate").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "processFederatedIdentityUpdate").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI identityProviderAuthnResponse(URI baseUri, String providerId, String realmName) {
|
public static URI identityProviderAuthnResponse(URI baseUri, String providerId, String realmName) {
|
||||||
|
@ -123,31 +123,31 @@ public class Urls {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountTotpPage(URI baseUri, String realmName) {
|
public static URI accountTotpPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.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, String stateChecker) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "processTotpRemove")
|
return accountBase(baseUri).path(AccountFormService.class, "processTotpRemove")
|
||||||
.queryParam("stateChecker", stateChecker)
|
.queryParam("stateChecker", stateChecker)
|
||||||
.build(realmName);
|
.build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountLogPage(URI baseUri, String realmName) {
|
public static URI accountLogPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "logPage").build(realmName);
|
return accountBase(baseUri).path(AccountFormService.class, "logPage").build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountSessionsPage(URI baseUri, String realmName) {
|
public static URI accountSessionsPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.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, String stateChecker) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "processSessionsLogout")
|
return accountBase(baseUri).path(AccountFormService.class, "processSessionsLogout")
|
||||||
.queryParam("stateChecker", stateChecker)
|
.queryParam("stateChecker", stateChecker)
|
||||||
.build(realmName);
|
.build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountRevokeClientPage(URI baseUri, String realmName) {
|
public static URI accountRevokeClientPage(URI baseUri, String realmName) {
|
||||||
return accountBase(baseUri).path(AccountService.class, "processRevokeGrant")
|
return accountBase(baseUri).path(AccountFormService.class, "processRevokeGrant")
|
||||||
.build(realmName);
|
.build(realmName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,10 @@ public class AppAuthManager extends AuthenticationManager {
|
||||||
return authenticateBearerToken(session, realm, ctx.getUri(), ctx.getConnection(), ctx.getRequestHeaders());
|
return authenticateBearerToken(session, realm, ctx.getUri(), ctx.getConnection(), ctx.getRequestHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthResult authenticateBearerToken(KeycloakSession session) {
|
||||||
|
return authenticateBearerToken(session, session.getContext().getRealm(), session.getContext().getUri(), session.getContext().getConnection(), session.getContext().getRequestHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
||||||
String tokenString = extractAuthorizationHeaderToken(headers);
|
String tokenString = extractAuthorizationHeaderToken(headers);
|
||||||
if (tokenString == null) return null;
|
if (tokenString == null) return null;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.services.ForbiddenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -79,6 +80,18 @@ public class Auth {
|
||||||
this.clientSession = clientSession;
|
this.clientSession = clientSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void require(String role) {
|
||||||
|
if (!hasClientRole(client, role)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requireOneOf(String... roles) {
|
||||||
|
if (!hasOneOfAppRole(client, roles)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasRealmRole(String role) {
|
public boolean hasRealmRole(String role) {
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
return user.hasRole(realm.getRole(role));
|
return user.hasRole(realm.getRole(role));
|
||||||
|
|
|
@ -136,6 +136,19 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void backchannelLogout(KeycloakSession session, UserSessionModel userSession, boolean logoutBroker) {
|
||||||
|
backchannelLogout(
|
||||||
|
session,
|
||||||
|
session.getContext().getRealm(),
|
||||||
|
userSession,
|
||||||
|
session.getContext().getUri(),
|
||||||
|
session.getContext().getConnection(),
|
||||||
|
session.getContext().getRequestHeaders(),
|
||||||
|
logoutBroker
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not logout broker
|
* Do not logout broker
|
||||||
*
|
*
|
||||||
|
|
|
@ -176,6 +176,8 @@ public class Messages {
|
||||||
|
|
||||||
public static final String READ_ONLY_USER = "readOnlyUserMessage";
|
public static final String READ_ONLY_USER = "readOnlyUserMessage";
|
||||||
|
|
||||||
|
public static final String READ_ONLY_USERNAME = "readOnlyUsernameMessage";
|
||||||
|
|
||||||
public static final String READ_ONLY_PASSWORD = "readOnlyPasswordMessage";
|
public static final String READ_ONLY_PASSWORD = "readOnlyPasswordMessage";
|
||||||
|
|
||||||
public static final String SUCCESS_TOTP_REMOVED = "successTotpRemovedMessage";
|
public static final String SUCCESS_TOTP_REMOVED = "successTotpRemovedMessage";
|
||||||
|
|
|
@ -203,32 +203,6 @@ public abstract class AbstractSecuredLocalService {
|
||||||
return oauth.redirect(uriInfo, accountUri.toString());
|
return oauth.redirect(uriInfo, accountUri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response authenticateBrowser() {
|
|
||||||
AppAuthManager authManager = new AppAuthManager();
|
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
|
|
||||||
if (authResult != null) {
|
|
||||||
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
|
|
||||||
} else {
|
|
||||||
return login(null);
|
|
||||||
}
|
|
||||||
// don't allow cors requests
|
|
||||||
// This is to prevent CSRF attacks.
|
|
||||||
String requestOrigin = UriUtils.getOrigin(uriInfo.getBaseUri());
|
|
||||||
String origin = headers.getRequestHeaders().getFirst("Origin");
|
|
||||||
if (origin != null && !requestOrigin.equals(origin)) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.getHttpMethod().equals("GET")) {
|
|
||||||
String referrer = headers.getRequestHeaders().getFirst("Referer");
|
|
||||||
if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateCsrfChecks();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class OAuthRedirect extends AbstractOAuthClient {
|
static class OAuthRedirect extends AbstractOAuthClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -108,28 +108,32 @@ public class Cors {
|
||||||
|
|
||||||
public Cors allowedOrigins(String... allowedOrigins) {
|
public Cors allowedOrigins(String... allowedOrigins) {
|
||||||
if (allowedOrigins != null && allowedOrigins.length > 0) {
|
if (allowedOrigins != null && allowedOrigins.length > 0) {
|
||||||
this.allowedOrigins = new HashSet<String>(Arrays.asList(allowedOrigins));
|
this.allowedOrigins = new HashSet<>(Arrays.asList(allowedOrigins));
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cors allowedMethods(String... allowedMethods) {
|
public Cors allowedMethods(String... allowedMethods) {
|
||||||
this.allowedMethods = new HashSet<String>(Arrays.asList(allowedMethods));
|
this.allowedMethods = new HashSet<>(Arrays.asList(allowedMethods));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cors exposedHeaders(String... exposedHeaders) {
|
public Cors exposedHeaders(String... exposedHeaders) {
|
||||||
this.exposedHeaders = new HashSet<String>(Arrays.asList(exposedHeaders));
|
this.exposedHeaders = new HashSet<>(Arrays.asList(exposedHeaders));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response build() {
|
public Response build() {
|
||||||
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
||||||
if (origin == null) {
|
if (origin == null) {
|
||||||
|
logger.trace("No origin header ignoring");
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
|
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugv("Invalid CORS request: origin {0} not in allowed origins {1}", origin, Arrays.toString(allowedOrigins.toArray()));
|
||||||
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,23 +169,25 @@ public class Cors {
|
||||||
builder.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
builder.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug("Added CORS headers to response");
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(HttpResponse response) {
|
public void build(HttpResponse response) {
|
||||||
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
||||||
if (origin == null) {
|
if (origin == null) {
|
||||||
logger.debug("No origin returning");
|
logger.trace("No origin header ignoring");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
|
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
|
||||||
logger.debug("!preflight and no origin");
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugv("Invalid CORS request: origin {0} not in allowed origins {1}", origin, Arrays.toString(allowedOrigins.toArray()));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("build CORS headers and return");
|
|
||||||
|
|
||||||
if (allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)) {
|
if (allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)) {
|
||||||
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD);
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD);
|
||||||
} else {
|
} else {
|
||||||
|
@ -213,6 +219,8 @@ public class Cors {
|
||||||
if (preflight) {
|
if (preflight) {
|
||||||
response.getOutputHeaders().add(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
response.getOutputHeaders().add(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug("Added CORS headers to response");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.ObjectUtil;
|
import org.keycloak.common.util.ObjectUtil;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.common.util.UriUtils;
|
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -77,6 +76,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.services.util.BrowserHistoryHelper;
|
import org.keycloak.services.util.BrowserHistoryHelper;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
@ -1082,7 +1082,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
FormMessage errorMessage = new FormMessage(message, parameters);
|
FormMessage errorMessage = new FormMessage(message, parameters);
|
||||||
try {
|
try {
|
||||||
String serializedError = JsonSerialization.writeValueAsString(errorMessage);
|
String serializedError = JsonSerialization.writeValueAsString(errorMessage);
|
||||||
authSession.setAuthNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
|
authSession.setAuthNote(AccountFormService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException(ioe);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
||||||
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.services.resources.admin.AdminRoot;
|
import org.keycloak.services.resources.admin.AdminRoot;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
@ -91,7 +92,7 @@ public class PublicRealmResource {
|
||||||
PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
|
PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
|
||||||
rep.setRealm(realm.getName());
|
rep.setRealm(realm.getName());
|
||||||
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||||
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
rep.setAccountServiceUrl(AccountFormService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||||
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
|
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
|
||||||
rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
|
rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
|
||||||
rep.setNotBefore(realm.getNotBefore());
|
rep.setNotBefore(realm.getNotBefore());
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
@ -34,6 +33,7 @@ import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resource.RealmResourceProvider;
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
|
import org.keycloak.services.resources.account.AccountLoader;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
import org.keycloak.services.util.ResolveRelative;
|
import org.keycloak.services.util.ResolveRelative;
|
||||||
import org.keycloak.utils.ProfileHelper;
|
import org.keycloak.utils.ProfileHelper;
|
||||||
|
@ -206,20 +206,10 @@ public class RealmsResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{realm}/account")
|
@Path("{realm}/account")
|
||||||
public AccountService getAccountService(final @PathParam("realm") String name) {
|
public Object getAccountService(final @PathParam("realm") String name) {
|
||||||
RealmModel realm = init(name);
|
RealmModel realm = init(name);
|
||||||
|
|
||||||
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
|
||||||
if (client == null || !client.isEnabled()) {
|
|
||||||
logger.debug("account management not enabled");
|
|
||||||
throw new NotFoundException("account management not enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||||
AccountService accountService = new AccountService(realm, client, event);
|
return AccountLoader.getAccountService(session, event);
|
||||||
ResteasyProviderFactory.getInstance().injectProperties(accountService);
|
|
||||||
accountService.init();
|
|
||||||
return accountService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{realm}")
|
@Path("{realm}")
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources.account;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
@ -44,9 +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.ModelToRepresentation;
|
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
|
||||||
import org.keycloak.services.ForbiddenException;
|
import org.keycloak.services.ForbiddenException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
@ -55,14 +53,17 @@ import org.keycloak.services.managers.Auth;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.UserSessionManager;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.services.resources.AbstractSecuredLocalService;
|
||||||
|
import org.keycloak.services.resources.AttributeFormDataProcessor;
|
||||||
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.util.ResolveRelative;
|
import org.keycloak.services.util.ResolveRelative;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
import org.keycloak.storage.ReadOnlyException;
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.utils.MediaType;
|
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -83,13 +84,13 @@ import java.util.UUID;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class AccountService extends AbstractSecuredLocalService {
|
public class AccountFormService extends AbstractSecuredLocalService {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(AccountService.class);
|
private static final Logger logger = Logger.getLogger(AccountFormService.class);
|
||||||
|
|
||||||
private static Set<String> VALID_PATHS = new HashSet<String>();
|
private static Set<String> VALID_PATHS = new HashSet<String>();
|
||||||
static {
|
static {
|
||||||
for (Method m : AccountService.class.getMethods()) {
|
for (Method m : AccountFormService.class.getMethods()) {
|
||||||
Path p = m.getAnnotation(Path.class);
|
Path p = m.getAnnotation(Path.class);
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
VALID_PATHS.add(p.value());
|
VALID_PATHS.add(p.value());
|
||||||
|
@ -97,20 +98,6 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final EventType[] LOG_EVENTS = {EventType.LOGIN, EventType.LOGOUT, EventType.REGISTER, EventType.REMOVE_FEDERATED_IDENTITY, EventType.REMOVE_TOTP, EventType.SEND_RESET_PASSWORD,
|
|
||||||
EventType.SEND_VERIFY_EMAIL, EventType.FEDERATED_IDENTITY_LINK, EventType.UPDATE_EMAIL, EventType.UPDATE_PASSWORD, EventType.UPDATE_PROFILE, EventType.UPDATE_TOTP, EventType.VERIFY_EMAIL};
|
|
||||||
|
|
||||||
private static final Set<String> LOG_DETAILS = new HashSet<String>();
|
|
||||||
static {
|
|
||||||
LOG_DETAILS.add(Details.UPDATED_EMAIL);
|
|
||||||
LOG_DETAILS.add(Details.EMAIL);
|
|
||||||
LOG_DETAILS.add(Details.PREVIOUS_EMAIL);
|
|
||||||
LOG_DETAILS.add(Details.USERNAME);
|
|
||||||
LOG_DETAILS.add(Details.REMEMBER_ME);
|
|
||||||
LOG_DETAILS.add(Details.REGISTER_METHOD);
|
|
||||||
LOG_DETAILS.add(Details.AUTH_METHOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used when some other context (ie. IdentityBrokerService) wants to forward error to account management and display it here
|
// Used when some other context (ie. IdentityBrokerService) wants to forward error to account management and display it here
|
||||||
public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR";
|
public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR";
|
||||||
|
|
||||||
|
@ -119,7 +106,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
private AccountProvider account;
|
private AccountProvider account;
|
||||||
private EventStoreProvider eventStore;
|
private EventStoreProvider eventStore;
|
||||||
|
|
||||||
public AccountService(RealmModel realm, ClientModel client, EventBuilder event) {
|
public AccountFormService(RealmModel realm, ClientModel client, EventBuilder event) {
|
||||||
super(realm, client);
|
super(realm, client);
|
||||||
this.event = event;
|
this.event = event;
|
||||||
this.authManager = new AppAuthManager();
|
this.authManager = new AppAuthManager();
|
||||||
|
@ -130,33 +117,24 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
|
|
||||||
account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo).setHttpHeaders(headers);
|
account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo).setHttpHeaders(headers);
|
||||||
|
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers);
|
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
|
||||||
if (authResult != null) {
|
if (authResult != null) {
|
||||||
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false);
|
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
|
||||||
} else {
|
updateCsrfChecks();
|
||||||
authResult = authManager.authenticateIdentityCookie(session, realm);
|
account.setStateChecker(stateChecker);
|
||||||
if (authResult != null) {
|
|
||||||
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
|
|
||||||
updateCsrfChecks();
|
|
||||||
account.setStateChecker(stateChecker);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String requestOrigin = UriUtils.getOrigin(uriInfo.getBaseUri());
|
String requestOrigin = UriUtils.getOrigin(uriInfo.getBaseUri());
|
||||||
|
|
||||||
// don't allow cors requests unless they were authenticated by an access token
|
String origin = headers.getRequestHeaders().getFirst("Origin");
|
||||||
// This is to prevent CSRF attacks.
|
if (origin != null && !requestOrigin.equals(origin)) {
|
||||||
if (auth != null && auth.isCookieAuthenticated()) {
|
throw new ForbiddenException();
|
||||||
String origin = headers.getRequestHeaders().getFirst("Origin");
|
}
|
||||||
if (origin != null && !requestOrigin.equals(origin)) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.getHttpMethod().equals("GET")) {
|
if (!request.getHttpMethod().equals("GET")) {
|
||||||
String referrer = headers.getRequestHeaders().getFirst("Referer");
|
String referrer = headers.getRequestHeaders().getFirst("Referer");
|
||||||
if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
|
if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,13 +149,9 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
account.setUser(auth.getUser());
|
account.setUser(auth.getUser());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean eventsEnabled = eventStore != null && realm.isEventsEnabled();
|
account.setFeatures(realm.isIdentityFederationEnabled(), eventStore != null && realm.isEventsEnabled(), true);
|
||||||
|
|
||||||
// todo find out from federation if password is updatable
|
|
||||||
account.setFeatures(realm.isIdentityFederationEnabled(), eventsEnabled, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
|
public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
|
||||||
|
@ -186,21 +160,17 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder accountServiceApplicationPage(UriInfo uriInfo) {
|
public static UriBuilder accountServiceApplicationPage(UriInfo uriInfo) {
|
||||||
return accountServiceBaseUrl(uriInfo).path(AccountService.class, "applicationsPage");
|
return accountServiceBaseUrl(uriInfo).path(AccountFormService.class, "applicationsPage");
|
||||||
}
|
|
||||||
|
|
||||||
public static UriBuilder accountServiceBaseUrl(UriBuilder base) {
|
|
||||||
return base.path(RealmsResource.class).path(RealmsResource.class, "getAccountService");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Set<String> getValidPaths() {
|
protected Set<String> getValidPaths() {
|
||||||
return AccountService.VALID_PATHS;
|
return AccountFormService.VALID_PATHS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response forwardToPage(String path, AccountPages page) {
|
private Response forwardToPage(String path, AccountPages page) {
|
||||||
if (auth != null) {
|
if (auth != null) {
|
||||||
try {
|
try {
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
} catch (ForbiddenException e) {
|
} catch (ForbiddenException e) {
|
||||||
return session.getProvider(LoginFormsProvider.class).setError(Messages.NO_ACCESS).createErrorPage();
|
return session.getProvider(LoginFormsProvider.class).setError(Messages.NO_ACCESS).createErrorPage();
|
||||||
}
|
}
|
||||||
|
@ -228,24 +198,13 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setReferrerOnPage() {
|
private void setReferrerOnPage() {
|
||||||
String[] referrer = getReferrer();
|
String[] referrer = getReferrer();
|
||||||
if (referrer != null) {
|
if (referrer != null) {
|
||||||
account.setReferrer(referrer);
|
account.setReferrer(referrer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* CORS preflight
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Path("/")
|
|
||||||
@OPTIONS
|
|
||||||
public Response accountPreflight() {
|
|
||||||
return Cors.add(request, Response.ok()).auth().preflight().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get account information.
|
* Get account information.
|
||||||
*
|
*
|
||||||
|
@ -253,28 +212,13 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
*/
|
*/
|
||||||
@Path("/")
|
@Path("/")
|
||||||
@GET
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
public Response accountPage() {
|
public Response accountPage() {
|
||||||
if (session.getContext().getRequestHeaders().getAcceptableMediaTypes().contains(MediaType.APPLICATION_JSON_TYPE)) {
|
return forwardToPage(null, AccountPages.ACCOUNT);
|
||||||
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
|
||||||
|
|
||||||
UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
|
|
||||||
if (rep.getAttributes() != null) {
|
|
||||||
Iterator<String> itr = rep.getAttributes().keySet().iterator();
|
|
||||||
while (itr.hasNext()) {
|
|
||||||
if (itr.next().startsWith("keycloak.")) {
|
|
||||||
itr.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Cors.add(request, Response.ok(rep).type(MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(auth.getToken()).build();
|
|
||||||
} else {
|
|
||||||
return forwardToPage(null, AccountPages.ACCOUNT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder totpUrl(UriBuilder base) {
|
public static UriBuilder totpUrl(UriBuilder base) {
|
||||||
return RealmsResource.accountUrl(base).path(AccountService.class, "totpPage");
|
return RealmsResource.accountUrl(base).path(AccountFormService.class, "totpPage");
|
||||||
}
|
}
|
||||||
@Path("totp")
|
@Path("totp")
|
||||||
@GET
|
@GET
|
||||||
|
@ -283,7 +227,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder passwordUrl(UriBuilder base) {
|
public static UriBuilder passwordUrl(UriBuilder base) {
|
||||||
return RealmsResource.accountUrl(base).path(AccountService.class, "passwordPage");
|
return RealmsResource.accountUrl(base).path(AccountFormService.class, "passwordPage");
|
||||||
}
|
}
|
||||||
@Path("password")
|
@Path("password")
|
||||||
@GET
|
@GET
|
||||||
|
@ -305,12 +249,12 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
@GET
|
@GET
|
||||||
public Response logPage() {
|
public Response logPage() {
|
||||||
if (auth != null) {
|
if (auth != null) {
|
||||||
List<Event> events = eventStore.createQuery().type(LOG_EVENTS).user(auth.getUser().getId()).maxResults(30).getResultList();
|
List<Event> events = eventStore.createQuery().type(Constants.EXPOSED_LOG_EVENTS).user(auth.getUser().getId()).maxResults(30).getResultList();
|
||||||
for (Event e : events) {
|
for (Event e : events) {
|
||||||
if (e.getDetails() != null) {
|
if (e.getDetails() != null) {
|
||||||
Iterator<Map.Entry<String, String>> itr = e.getDetails().entrySet().iterator();
|
Iterator<Map.Entry<String, String>> itr = e.getDetails().entrySet().iterator();
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
if (!LOG_DETAILS.contains(itr.next().getKey())) {
|
if (!Constants.EXPOSED_LOG_DETAILS.contains(itr.next().getKey())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,7 +300,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login(null);
|
return login(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
String action = formData.getFirst("submitAction");
|
String action = formData.getFirst("submitAction");
|
||||||
if (action != null && action.equals("Cancel")) {
|
if (action != null && action.equals("Cancel")) {
|
||||||
|
@ -377,8 +321,8 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
updateUsername(formData.getFirst("username"), user);
|
updateUsername(formData.getFirst("username"), user, session);
|
||||||
updateEmail(formData.getFirst("email"), user);
|
updateEmail(formData.getFirst("email"), user, session, event);
|
||||||
|
|
||||||
user.setFirstName(formData.getFirst("firstName"));
|
user.setFirstName(formData.getFirst("firstName"));
|
||||||
user.setLastName(formData.getFirst("lastName"));
|
user.setLastName(formData.getFirst("lastName"));
|
||||||
|
@ -398,82 +342,6 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("/")
|
|
||||||
@POST
|
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
public Response processAccountUpdateJson(UserRepresentation userRep) {
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
|
||||||
if (auth.isCookieAuthenticated()) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
|
||||||
|
|
||||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
|
|
||||||
|
|
||||||
updateUsername(userRep.getUsername(), user);
|
|
||||||
updateEmail(userRep.getEmail(), user);
|
|
||||||
|
|
||||||
user.setFirstName(userRep.getFirstName());
|
|
||||||
user.setLastName(userRep.getLastName());
|
|
||||||
|
|
||||||
if (userRep.getAttributes() != null) {
|
|
||||||
for (String k : user.getAttributes().keySet()) {
|
|
||||||
if (!userRep.getAttributes().containsKey(k)) {
|
|
||||||
user.removeAttribute(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> e : userRep.getAttributes().entrySet()) {
|
|
||||||
user.setAttribute(e.getKey(), e.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event.success();
|
|
||||||
|
|
||||||
return Cors.add(request, Response.ok()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUsername(String username, UserModel user) {
|
|
||||||
if (realm.isEditUsernameAllowed() && username != null) {
|
|
||||||
UserModel existing = session.users().getUserByUsername(username, realm);
|
|
||||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
|
||||||
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
user.setUsername(username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateEmail(String email, UserModel user) {
|
|
||||||
String oldEmail = user.getEmail();
|
|
||||||
boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
|
|
||||||
if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
|
|
||||||
UserModel existing = session.users().getUserByEmail(email, realm);
|
|
||||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
|
||||||
throw new ModelDuplicateException(Messages.EMAIL_EXISTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user.setEmail(email);
|
|
||||||
|
|
||||||
if (emailChanged) {
|
|
||||||
user.setEmailVerified(false);
|
|
||||||
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realm.isRegistrationEmailAsUsername()) {
|
|
||||||
if (!realm.isDuplicateEmailsAllowed()) {
|
|
||||||
UserModel existing = session.users().getUserByEmail(email, realm);
|
|
||||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
|
||||||
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user.setUsername(email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("totp-remove")
|
@Path("totp-remove")
|
||||||
@GET
|
@GET
|
||||||
public Response processTotpRemove(@QueryParam("stateChecker") String stateChecker) {
|
public Response processTotpRemove(@QueryParam("stateChecker") String stateChecker) {
|
||||||
|
@ -481,7 +349,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login("totp");
|
return login("totp");
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(stateChecker);
|
||||||
|
|
||||||
|
@ -502,7 +370,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login("sessions");
|
return login("sessions");
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(stateChecker);
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
@ -516,7 +384,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "sessionsPage");
|
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountFormService.class, "sessionsPage");
|
||||||
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
||||||
if (referrer != null) {
|
if (referrer != null) {
|
||||||
builder.queryParam("referrer", referrer);
|
builder.queryParam("referrer", referrer);
|
||||||
|
@ -534,7 +402,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login("applications");
|
return login("applications");
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
csrfCheck(formData);
|
csrfCheck(formData);
|
||||||
|
|
||||||
String clientId = formData.getFirst("clientId");
|
String clientId = formData.getFirst("clientId");
|
||||||
|
@ -557,7 +425,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
event.event(EventType.REVOKE_GRANT).client(auth.getClient()).user(auth.getUser()).detail(Details.REVOKED_CLIENT, client.getClientId()).success();
|
event.event(EventType.REVOKE_GRANT).client(auth.getClient()).user(auth.getUser()).detail(Details.REVOKED_CLIENT, client.getClientId()).success();
|
||||||
setReferrerOnPage();
|
setReferrerOnPage();
|
||||||
|
|
||||||
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "applicationsPage");
|
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountFormService.class, "applicationsPage");
|
||||||
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
||||||
if (referrer != null) {
|
if (referrer != null) {
|
||||||
builder.queryParam("referrer", referrer);
|
builder.queryParam("referrer", referrer);
|
||||||
|
@ -586,7 +454,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login("totp");
|
return login("totp");
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
String action = formData.getFirst("submitAction");
|
String action = formData.getFirst("submitAction");
|
||||||
if (action != null && action.equals("Cancel")) {
|
if (action != null && action.equals("Cancel")) {
|
||||||
|
@ -646,7 +514,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login("password");
|
return login("password");
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
csrfCheck(formData);
|
csrfCheck(formData);
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
@ -729,7 +597,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return login("identity");
|
return login("identity");
|
||||||
}
|
}
|
||||||
|
|
||||||
require(AccountRoles.MANAGE_ACCOUNT);
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(stateChecker);
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
|
@ -816,7 +684,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder loginRedirectUrl(UriBuilder base) {
|
public static UriBuilder loginRedirectUrl(UriBuilder base) {
|
||||||
return RealmsResource.accountUrl(base).path(AccountService.class, "loginRedirect");
|
return RealmsResource.accountUrl(base).path(AccountFormService.class, "loginRedirect");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -865,27 +733,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void require(String role) {
|
private enum AccountSocialAction {
|
||||||
if (auth == null) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!auth.hasClientRole(client, role)) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requireOneOf(String... roles) {
|
|
||||||
if (auth == null) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!auth.hasOneOfAppRole(client, roles)) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AccountSocialAction {
|
|
||||||
ADD,
|
ADD,
|
||||||
REMOVE;
|
REMOVE;
|
||||||
|
|
||||||
|
@ -899,4 +747,52 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateUsername(String username, UserModel user, KeycloakSession session) {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
boolean usernameChanged = username == null || !user.getUsername().equals(username);
|
||||||
|
if (realm.isEditUsernameAllowed()) {
|
||||||
|
if (usernameChanged) {
|
||||||
|
UserModel existing = session.users().getUserByUsername(username, realm);
|
||||||
|
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||||
|
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setUsername(username);
|
||||||
|
}
|
||||||
|
} else if (usernameChanged) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmail(String email, UserModel user, KeycloakSession session, EventBuilder event) {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
String oldEmail = user.getEmail();
|
||||||
|
boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
|
||||||
|
if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
|
||||||
|
UserModel existing = session.users().getUserByEmail(email, realm);
|
||||||
|
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||||
|
throw new ModelDuplicateException(Messages.EMAIL_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setEmail(email);
|
||||||
|
|
||||||
|
if (emailChanged) {
|
||||||
|
user.setEmailVerified(false);
|
||||||
|
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realm.isRegistrationEmailAsUsername()) {
|
||||||
|
if (!realm.isDuplicateEmailsAllowed()) {
|
||||||
|
UserModel existing = session.users().getUserByEmail(email, realm);
|
||||||
|
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||||
|
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user.setUsername(email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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.services.resources.account;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
|
import org.keycloak.services.managers.Auth;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
|
||||||
|
import javax.ws.rs.HttpMethod;
|
||||||
|
import javax.ws.rs.NotAuthorizedException;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AccountLoader {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(AccountLoader.class);
|
||||||
|
|
||||||
|
private AccountLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getAccountService(KeycloakSession session, EventBuilder event) {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
|
||||||
|
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||||
|
if (client == null || !client.isEnabled()) {
|
||||||
|
logger.debug("account management not enabled");
|
||||||
|
throw new NotFoundException("account management not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest request = session.getContext().getContextObject(HttpRequest.class);
|
||||||
|
HttpHeaders headers = session.getContext().getRequestHeaders();
|
||||||
|
MediaType content = headers.getMediaType();
|
||||||
|
List<MediaType> accepts = headers.getAcceptableMediaTypes();
|
||||||
|
|
||||||
|
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
|
||||||
|
return new CorsPreflightService(request);
|
||||||
|
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !request.getUri().getPath().endsWith("keycloak.json")) {
|
||||||
|
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
|
||||||
|
if (authResult == null) {
|
||||||
|
throw new NotAuthorizedException("Bearer token required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth auth = new Auth(session.getContext().getRealm(), authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false);
|
||||||
|
AccountRestService accountRestService = new AccountRestService(session, auth, client, event);
|
||||||
|
ResteasyProviderFactory.getInstance().injectProperties(accountRestService);
|
||||||
|
accountRestService.init();
|
||||||
|
return accountRestService;
|
||||||
|
} else {
|
||||||
|
AccountFormService accountFormService = new AccountFormService(realm, client, event);
|
||||||
|
ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
|
||||||
|
accountFormService.init();
|
||||||
|
return accountFormService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
* 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.services.resources.account;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.common.ClientConnection;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.AccountRoles;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.representations.account.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.account.SessionRepresentation;
|
||||||
|
import org.keycloak.representations.account.UserRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
import org.keycloak.services.managers.Auth;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.resources.Cors;
|
||||||
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.OPTIONS;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AccountRestService {
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private HttpRequest request;
|
||||||
|
@Context
|
||||||
|
protected UriInfo uriInfo;
|
||||||
|
@Context
|
||||||
|
protected HttpHeaders headers;
|
||||||
|
@Context
|
||||||
|
protected ClientConnection clientConnection;
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final ClientModel client;
|
||||||
|
private final EventBuilder event;
|
||||||
|
private EventStoreProvider eventStore;
|
||||||
|
private Auth auth;
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final UserModel user;
|
||||||
|
|
||||||
|
public AccountRestService(KeycloakSession session, Auth auth, ClientModel client, EventBuilder event) {
|
||||||
|
this.session = session;
|
||||||
|
this.auth = auth;
|
||||||
|
this.realm = auth.getRealm();
|
||||||
|
this.user = auth.getUser();
|
||||||
|
this.client = client;
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CORS preflight
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("/")
|
||||||
|
@OPTIONS
|
||||||
|
@NoCache
|
||||||
|
public Response preflight() {
|
||||||
|
return Cors.add(request, Response.ok()).auth().preflight().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get account information.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("/")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@NoCache
|
||||||
|
public Response account() {
|
||||||
|
auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
||||||
|
|
||||||
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
|
UserRepresentation rep = new UserRepresentation();
|
||||||
|
rep.setUsername(user.getUsername());
|
||||||
|
rep.setFirstName(user.getFirstName());
|
||||||
|
rep.setLastName(user.getLastName());
|
||||||
|
rep.setEmail(user.getEmail());
|
||||||
|
rep.setEmailVerified(user.isEmailVerified());
|
||||||
|
rep.setAttributes(user.getAttributes());
|
||||||
|
|
||||||
|
return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/")
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@NoCache
|
||||||
|
public Response updateAccount(UserRepresentation userRep) {
|
||||||
|
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||||
|
|
||||||
|
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
|
||||||
|
boolean usernameChanged = userRep.getUsername() != null && !userRep.getUsername().equals(user.getUsername());
|
||||||
|
if (realm.isEditUsernameAllowed()) {
|
||||||
|
if (usernameChanged) {
|
||||||
|
UserModel existing = session.users().getUserByUsername(userRep.getUsername(), realm);
|
||||||
|
if (existing != null) {
|
||||||
|
return ErrorResponse.exists(Errors.USERNAME_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setUsername(userRep.getUsername());
|
||||||
|
}
|
||||||
|
} else if (usernameChanged) {
|
||||||
|
return ErrorResponse.error(Errors.READ_ONLY_USERNAME, Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean emailChanged = userRep.getEmail() != null && !userRep.getEmail().equals(user.getEmail());
|
||||||
|
if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
|
||||||
|
UserModel existing = session.users().getUserByEmail(userRep.getEmail(), realm);
|
||||||
|
if (existing != null) {
|
||||||
|
return ErrorResponse.exists(Errors.EMAIL_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realm.isRegistrationEmailAsUsername() && !realm.isDuplicateEmailsAllowed()) {
|
||||||
|
UserModel existing = session.users().getUserByUsername(userRep.getEmail(), realm);
|
||||||
|
if (existing != null) {
|
||||||
|
return ErrorResponse.exists(Errors.USERNAME_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emailChanged) {
|
||||||
|
String oldEmail = user.getEmail();
|
||||||
|
user.setEmail(userRep.getEmail());
|
||||||
|
user.setEmailVerified(false);
|
||||||
|
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, userRep.getEmail()).success();
|
||||||
|
|
||||||
|
if (realm.isRegistrationEmailAsUsername()) {
|
||||||
|
user.setUsername(userRep.getEmail());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setFirstName(userRep.getFirstName());
|
||||||
|
user.setLastName(userRep.getLastName());
|
||||||
|
|
||||||
|
if (userRep.getAttributes() != null) {
|
||||||
|
for (String k : user.getAttributes().keySet()) {
|
||||||
|
if (!userRep.getAttributes().containsKey(k)) {
|
||||||
|
user.removeAttribute(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> e : userRep.getAttributes().entrySet()) {
|
||||||
|
user.setAttribute(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.success();
|
||||||
|
|
||||||
|
return Cors.add(request, Response.ok()).auth().allowedOrigins(auth.getToken()).build();
|
||||||
|
} catch (ReadOnlyException e) {
|
||||||
|
return ErrorResponse.error(Errors.READ_ONLY_USER, Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get session information.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("/sessions")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@NoCache
|
||||||
|
public Response sessions() {
|
||||||
|
List<SessionRepresentation> reps = new LinkedList<>();
|
||||||
|
|
||||||
|
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
|
||||||
|
for (UserSessionModel s : sessions) {
|
||||||
|
SessionRepresentation rep = new SessionRepresentation();
|
||||||
|
rep.setId(s.getId());
|
||||||
|
rep.setIpAddress(s.getIpAddress());
|
||||||
|
rep.setStarted(s.getStarted());
|
||||||
|
rep.setLastAccess(s.getLastSessionRefresh());
|
||||||
|
rep.setExpires(s.getStarted() + realm.getSsoSessionMaxLifespan());
|
||||||
|
rep.setClients(new LinkedList());
|
||||||
|
|
||||||
|
for (String clientUUID : s.getAuthenticatedClientSessions().keySet()) {
|
||||||
|
ClientModel client = realm.getClientById(clientUUID);
|
||||||
|
ClientRepresentation clientRep = new ClientRepresentation();
|
||||||
|
clientRep.setClientId(client.getClientId());
|
||||||
|
clientRep.setClientName(client.getName());
|
||||||
|
rep.getClients().add(clientRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
reps.add(rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cors.add(request, Response.ok(reps)).auth().allowedOrigins(auth.getToken()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove sessions
|
||||||
|
*
|
||||||
|
* @param removeCurrent remove current session (default is false)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("/sessions")
|
||||||
|
@DELETE
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@NoCache
|
||||||
|
public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) {
|
||||||
|
UserSessionModel userSession = auth.getSession();
|
||||||
|
|
||||||
|
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||||
|
for (UserSessionModel s : userSessions) {
|
||||||
|
if (removeCurrent || !s.getId().equals(userSession.getId())) {
|
||||||
|
AuthenticationManager.backchannelLogout(session, s, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cors.add(request, Response.ok()).auth().allowedOrigins(auth.getToken()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Federated identities
|
||||||
|
// TODO Applications
|
||||||
|
// TODO Logs
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.services.resources.account;
|
||||||
|
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class Constants {
|
||||||
|
|
||||||
|
public static final EventType[] EXPOSED_LOG_EVENTS = {
|
||||||
|
EventType.LOGIN, EventType.LOGOUT, EventType.REGISTER, EventType.REMOVE_FEDERATED_IDENTITY, EventType.REMOVE_TOTP, EventType.SEND_RESET_PASSWORD,
|
||||||
|
EventType.SEND_VERIFY_EMAIL, EventType.FEDERATED_IDENTITY_LINK, EventType.UPDATE_EMAIL, EventType.UPDATE_PASSWORD, EventType.UPDATE_PROFILE, EventType.UPDATE_TOTP, EventType.VERIFY_EMAIL
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Set<String> EXPOSED_LOG_DETAILS = new HashSet<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.UPDATED_EMAIL);
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.EMAIL);
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.PREVIOUS_EMAIL);
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.USERNAME);
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.REMEMBER_ME);
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.REGISTER_METHOD);
|
||||||
|
EXPOSED_LOG_DETAILS.add(Details.AUTH_METHOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.keycloak.services.resources.account;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.services.resources.Cors;
|
||||||
|
|
||||||
|
import javax.ws.rs.OPTIONS;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by st on 21/03/17.
|
||||||
|
*/
|
||||||
|
public class CorsPreflightService {
|
||||||
|
|
||||||
|
private HttpRequest request;
|
||||||
|
|
||||||
|
public CorsPreflightService(HttpRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CORS preflight
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("/")
|
||||||
|
@OPTIONS
|
||||||
|
public Response preflight() {
|
||||||
|
Cors cors = Cors.add(request, Response.ok()).auth().allowedMethods("GET", "POST", "HEAD", "OPTIONS").preflight();
|
||||||
|
return cors.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.services.resources.account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class Errors {
|
||||||
|
|
||||||
|
public static final String USERNAME_EXISTS = "username_exists";
|
||||||
|
public static final String EMAIL_EXISTS = "email_exists";
|
||||||
|
public static final String READ_ONLY_USER = "user_read_only";
|
||||||
|
public static final String READ_ONLY_USERNAME = "username_read_only";
|
||||||
|
|
||||||
|
}
|
|
@ -68,8 +68,8 @@ import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
import org.keycloak.services.managers.UserSessionManager;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.services.resources.AccountService;
|
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.storage.ReadOnlyException;
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
|
@ -282,7 +282,7 @@ public class UserResource {
|
||||||
String sessionId = KeycloakModelUtils.generateId();
|
String sessionId = KeycloakModelUtils.generateId();
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
|
UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
|
||||||
AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||||
URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
|
URI redirect = AccountFormService.accountServiceApplicationPage(uriInfo).build(realm.getName());
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("sameRealm", sameRealm);
|
result.put("sameRealm", sameRealm);
|
||||||
result.put("redirect", redirect.toString());
|
result.put("redirect", redirect.toString());
|
||||||
|
|
|
@ -68,7 +68,6 @@ import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.BruteForceProtector;
|
import org.keycloak.services.managers.BruteForceProtector;
|
||||||
import org.keycloak.services.*;
|
import org.keycloak.services.*;
|
||||||
import org.keycloak.services.managers.*;
|
import org.keycloak.services.managers.*;
|
||||||
import org.keycloak.services.resources.AccountService;
|
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.storage.ReadOnlyException;
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
@ -69,6 +69,6 @@ public class AccountPasswordPage extends AbstractAccountPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
return AccountService.passwordUrl(UriBuilder.fromUri(getAuthServerRoot())).build(this.realmName).toString();
|
return AccountFormService.passwordUrl(UriBuilder.fromUri(getAuthServerRoot())).build(this.realmName).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ public class AccountTotpPage extends AbstractAccountPage {
|
||||||
private WebElement removeLink;
|
private WebElement removeLink;
|
||||||
|
|
||||||
private String getPath() {
|
private String getPath() {
|
||||||
return AccountService.totpUrl(UriBuilder.fromUri(getAuthServerRoot())).build("test").toString();
|
return AccountFormService.totpUrl(UriBuilder.fromUri(getAuthServerRoot())).build("test").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void configure(String totp) {
|
public void configure(String totp) {
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
|
import org.junit.rules.TestRule;
|
||||||
|
import org.junit.runners.model.Statement;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by st on 22/03/17.
|
||||||
|
*/
|
||||||
|
public class TokenUtil implements TestRule {
|
||||||
|
|
||||||
|
private final String username;
|
||||||
|
private final String password;
|
||||||
|
private OAuthClient oauth;
|
||||||
|
|
||||||
|
private String refreshToken;
|
||||||
|
private String token;
|
||||||
|
private int expires;
|
||||||
|
|
||||||
|
public TokenUtil() {
|
||||||
|
this("test-user@localhost", "password");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenUtil(String username, String password) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.oauth = new OAuthClient();
|
||||||
|
this.oauth.init(null, null);
|
||||||
|
this.oauth.clientId("direct-grant");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement apply(final Statement base, org.junit.runner.Description description) {
|
||||||
|
return new Statement() {
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
base.evaluate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
if (refreshToken == null) {
|
||||||
|
load();
|
||||||
|
} else if (expires < Time.currentTime()) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
try {
|
||||||
|
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doGrantAccessTokenRequest("password", username, password);
|
||||||
|
if (accessTokenResponse.getStatusCode() != 200) {
|
||||||
|
fail("Failed to get token: " + accessTokenResponse.getErrorDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshToken = accessTokenResponse.getRefreshToken();
|
||||||
|
this.token = accessTokenResponse.getAccessToken();
|
||||||
|
|
||||||
|
expires = Time.currentTime() + accessTokenResponse.getExpiresIn() - 20;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
try {
|
||||||
|
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doRefreshTokenRequest(refreshToken, "password");
|
||||||
|
if (accessTokenResponse.getStatusCode() != 200) {
|
||||||
|
fail("Failed to get token: " + accessTokenResponse.getErrorDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshToken = accessTokenResponse.getRefreshToken();
|
||||||
|
this.token = accessTokenResponse.getAccessToken();
|
||||||
|
|
||||||
|
expires = Time.currentTime() + accessTokenResponse.getExpiresIn() - 20;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.logging.LogEntries;
|
||||||
|
import org.openqa.selenium.logging.LogEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by st on 21/03/17.
|
||||||
|
*/
|
||||||
|
public class WebDriverLogDumper {
|
||||||
|
|
||||||
|
public static String dumpBrowserLogs(WebDriver driver) {
|
||||||
|
try {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
LogEntries logEntries = driver.manage().logs().get("browser");
|
||||||
|
for (LogEntry e : logEntries.getAll()) {
|
||||||
|
sb.append("\n\t" + e.getMessage());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
return "Browser doesn't support fetching logs";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,12 +18,10 @@ package org.keycloak.testsuite.account;
|
||||||
|
|
||||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -37,10 +35,10 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.resources.AccountService;
|
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.drone.Different;
|
import org.keycloak.testsuite.drone.Different;
|
||||||
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
||||||
|
@ -59,7 +57,6 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
@ -68,13 +65,15 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.hasItems;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
public class AccountTest extends AbstractTestRealmKeycloakTest {
|
public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
@ -121,7 +120,7 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
private static final UriBuilder BASE = UriBuilder.fromUri("http://localhost:8180/auth");
|
private static final UriBuilder BASE = UriBuilder.fromUri("http://localhost:8180/auth");
|
||||||
private static final String ACCOUNT_URL = RealmsResource.accountUrl(BASE.clone()).build("test").toString();
|
private static final String ACCOUNT_URL = RealmsResource.accountUrl(BASE.clone()).build("test").toString();
|
||||||
public static String ACCOUNT_REDIRECT = AccountService.loginRedirectUrl(BASE.clone()).build("test").toString();
|
public static String ACCOUNT_REDIRECT = AccountFormService.loginRedirectUrl(BASE.clone()).build("test").toString();
|
||||||
|
|
||||||
// Create second session
|
// Create second session
|
||||||
@Drone
|
@Drone
|
||||||
|
@ -904,7 +903,7 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertTrue(applicationsPage.isCurrent());
|
Assert.assertTrue(applicationsPage.isCurrent());
|
||||||
|
|
||||||
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
|
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
|
||||||
Assert.assertThat(apps.keySet(), containsInAnyOrder("root-url-client", "Account", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}"));
|
Assert.assertThat(apps.keySet(), containsInAnyOrder("root-url-client", "Account", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant"));
|
||||||
|
|
||||||
AccountApplicationsPage.AppEntry accountEntry = apps.get("Account");
|
AccountApplicationsPage.AppEntry accountEntry = apps.get("Account");
|
||||||
Assert.assertEquals(3, accountEntry.getRolesAvailable().size());
|
Assert.assertEquals(3, accountEntry.getRolesAvailable().size());
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* 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.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.util.TokenUtil;
|
||||||
|
import org.keycloak.testsuite.util.WebDriverLogDumper;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AccountRestServiceCorsTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
private static final String VALID_CORS_URL = "http://localtest.me:8180/auth";
|
||||||
|
private static final String INVALID_CORS_URL = "http://invalid.localtest.me:8180/auth";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TokenUtil tokenUtil = new TokenUtil();
|
||||||
|
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
private JavascriptExecutor executor;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
client = HttpClientBuilder.create().build();
|
||||||
|
oauth.clientId("direct-grant");
|
||||||
|
executor = (JavascriptExecutor) driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetProfile() throws IOException, InterruptedException {
|
||||||
|
driver.navigate().to(VALID_CORS_URL);
|
||||||
|
|
||||||
|
doJsGet(executor, getAccountUrl(), tokenUtil.getToken(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetProfileInvalidOrigin() throws IOException, InterruptedException {
|
||||||
|
driver.navigate().to(INVALID_CORS_URL);
|
||||||
|
|
||||||
|
doJsGet(executor, getAccountUrl(), tokenUtil.getToken(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfile() throws IOException {
|
||||||
|
driver.navigate().to(VALID_CORS_URL);
|
||||||
|
|
||||||
|
doJsPost(executor, getAccountUrl(), tokenUtil.getToken(), "{ \"firstName\" : \"Bob\" }", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfileInvalidOrigin() throws IOException {
|
||||||
|
driver.navigate().to(INVALID_CORS_URL);
|
||||||
|
|
||||||
|
doJsPost(executor, getAccountUrl(), tokenUtil.getToken(), "{ \"firstName\" : \"Bob\" }", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAccountUrl() {
|
||||||
|
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result doJsGet(JavascriptExecutor executor, String url, String token, boolean expectAllowed) {
|
||||||
|
String js = "var r = new XMLHttpRequest();" +
|
||||||
|
"var r = new XMLHttpRequest();" +
|
||||||
|
"r.open('GET', '" + url + "', false);" +
|
||||||
|
"r.setRequestHeader('Accept','application/json');" +
|
||||||
|
"r.setRequestHeader('Authorization','bearer " + token + "');" +
|
||||||
|
"r.send();" +
|
||||||
|
"return r.status + ':::' + r.responseText";
|
||||||
|
return doXhr(executor, js, expectAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result doJsPost(JavascriptExecutor executor, String url, String token, String data, boolean expectAllowed) {
|
||||||
|
String js = "var r = new XMLHttpRequest();" +
|
||||||
|
"var r = new XMLHttpRequest();" +
|
||||||
|
"r.open('POST', '" + url + "', false);" +
|
||||||
|
"r.setRequestHeader('Accept','application/json');" +
|
||||||
|
"r.setRequestHeader('Content-Type','application/json');" +
|
||||||
|
"r.setRequestHeader('Authorization','bearer " + token + "');" +
|
||||||
|
"r.send('" + data + "');" +
|
||||||
|
"return r.status + ':::' + r.responseText";
|
||||||
|
return doXhr(executor, js, expectAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result doXhr(JavascriptExecutor executor, String js, boolean expectAllowed) {
|
||||||
|
Result result = null;
|
||||||
|
Throwable error = null;
|
||||||
|
try {
|
||||||
|
String response = (String) executor.executeScript(js);
|
||||||
|
String r[] = response.split(":::");
|
||||||
|
result = new Result(Integer.parseInt(r[0]), r.length == 2 ? r[1] : null);
|
||||||
|
} catch (Throwable t ) {
|
||||||
|
error = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null || result.getStatus() != 200 || error != null) {
|
||||||
|
if (expectAllowed) {
|
||||||
|
throw new AssertionError("Cors request failed: " + WebDriverLogDumper.dumpBrowserLogs(driver));
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!expectAllowed) {
|
||||||
|
throw new AssertionError("Expected CORS request to be rejected, but was successful");
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Result {
|
||||||
|
int status;
|
||||||
|
|
||||||
|
String result;
|
||||||
|
|
||||||
|
public Result(int status, String result) {
|
||||||
|
this.status = status;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
* 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 com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
|
import org.keycloak.representations.account.SessionRepresentation;
|
||||||
|
import org.keycloak.representations.account.UserRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.util.TokenUtil;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TokenUtil tokenUtil = new TokenUtil();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
client = HttpClientBuilder.create().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
testRealm.getUsers().add(UserBuilder.create().username("no-account-access").password("password").build());
|
||||||
|
testRealm.getUsers().add(UserBuilder.create().username("view-account-access").role("account", "view-profile").password("password").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetProfile() throws IOException {
|
||||||
|
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||||
|
assertEquals("Tom", user.getFirstName());
|
||||||
|
assertEquals("Brady", user.getLastName());
|
||||||
|
assertEquals("test-user@localhost", user.getEmail());
|
||||||
|
assertFalse(user.isEmailVerified());
|
||||||
|
assertTrue(user.getAttributes().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfile() throws IOException {
|
||||||
|
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||||
|
user.setFirstName("Homer");
|
||||||
|
user.setLastName("Simpsons");
|
||||||
|
user.getAttributes().put("attr1", Collections.singletonList("val1"));
|
||||||
|
user.getAttributes().put("attr2", Collections.singletonList("val2"));
|
||||||
|
|
||||||
|
user = updateAndGet(user);
|
||||||
|
|
||||||
|
assertEquals("Homer", user.getFirstName());
|
||||||
|
assertEquals("Simpsons", user.getLastName());
|
||||||
|
assertEquals(2, user.getAttributes().size());
|
||||||
|
assertEquals(1, user.getAttributes().get("attr1").size());
|
||||||
|
assertEquals("val1", user.getAttributes().get("attr1").get(0));
|
||||||
|
assertEquals(1, user.getAttributes().get("attr2").size());
|
||||||
|
assertEquals("val2", user.getAttributes().get("attr2").get(0));
|
||||||
|
|
||||||
|
// Update attributes
|
||||||
|
user.getAttributes().remove("attr1");
|
||||||
|
user.getAttributes().get("attr2").add("val3");
|
||||||
|
|
||||||
|
user = updateAndGet(user);
|
||||||
|
|
||||||
|
assertEquals(1, user.getAttributes().size());
|
||||||
|
assertEquals(2, user.getAttributes().get("attr2").size());
|
||||||
|
assertEquals("val2", user.getAttributes().get("attr2").get(0));
|
||||||
|
assertEquals("val3", user.getAttributes().get("attr2").get(1));
|
||||||
|
|
||||||
|
// Update email
|
||||||
|
user.setEmail("bobby@localhost");
|
||||||
|
user = updateAndGet(user);
|
||||||
|
assertEquals("bobby@localhost", user.getEmail());
|
||||||
|
|
||||||
|
user.setEmail("john-doh@localhost");
|
||||||
|
updateError(user, 409, "email_exists");
|
||||||
|
|
||||||
|
user.setEmail("test-user@localhost");
|
||||||
|
user = updateAndGet(user);
|
||||||
|
assertEquals("test-user@localhost", user.getEmail());
|
||||||
|
|
||||||
|
// Update username
|
||||||
|
user.setUsername("updatedUsername");
|
||||||
|
user = updateAndGet(user);
|
||||||
|
assertEquals("updatedusername", user.getUsername());
|
||||||
|
|
||||||
|
user.setUsername("john-doh@localhost");
|
||||||
|
updateError(user, 409, "username_exists");
|
||||||
|
|
||||||
|
user.setUsername("test-user@localhost");
|
||||||
|
user = updateAndGet(user);
|
||||||
|
assertEquals("test-user@localhost", user.getUsername());
|
||||||
|
|
||||||
|
RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
|
||||||
|
realmRep.setEditUsernameAllowed(false);
|
||||||
|
adminClient.realm("test").update(realmRep);
|
||||||
|
|
||||||
|
user.setUsername("updatedUsername2");
|
||||||
|
updateError(user, 400, "username_read_only");
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
|
||||||
|
int status = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asStatus();
|
||||||
|
assertEquals(200, status);
|
||||||
|
return SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateError(UserRepresentation user, int expectedStatus, String expectedMessage) throws IOException {
|
||||||
|
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asResponse();
|
||||||
|
assertEquals(expectedStatus, response.getStatus());
|
||||||
|
assertEquals(expectedMessage, response.asJson(ErrorRepresentation.class).getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProfilePermissions() throws IOException {
|
||||||
|
TokenUtil noaccessToken = new TokenUtil("no-account-access", "password");
|
||||||
|
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
|
||||||
|
|
||||||
|
// Read with no access
|
||||||
|
assertEquals(403, SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
|
||||||
|
|
||||||
|
// Update with no access
|
||||||
|
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(noaccessToken.getToken()).json(new UserRepresentation()).asStatus());
|
||||||
|
|
||||||
|
// Update with read only
|
||||||
|
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(viewToken.getToken()).json(new UserRepresentation()).asStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateProfilePermissions() throws IOException {
|
||||||
|
TokenUtil noaccessToken = new TokenUtil("no-account-access", "password");
|
||||||
|
int status = SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus();
|
||||||
|
assertEquals(403, status);
|
||||||
|
|
||||||
|
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
|
||||||
|
status = SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus();
|
||||||
|
assertEquals(200, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSessions() throws IOException {
|
||||||
|
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
|
||||||
|
|
||||||
|
assertEquals(1, sessions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAccountUrl(String resource) {
|
||||||
|
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account" + (resource != null ? "/" + resource : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,362 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.account.AccountTest;
|
import org.keycloak.testsuite.account.AccountFormServiceTest;
|
||||||
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
@ -68,7 +68,7 @@ public class CustomThemeTest extends AbstractTestRealmKeycloakTest {
|
||||||
profilePage.open();
|
profilePage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT).assertEvent();
|
events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountFormServiceTest.ACCOUNT_REDIRECT).assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||||
Assert.assertEquals("", profilePage.getAttribute("street"));
|
Assert.assertEquals("", profilePage.getAttribute("street"));
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.pages.AccountTotpPage;
|
import org.keycloak.testsuite.pages.AccountTotpPage;
|
||||||
|
@ -45,7 +45,7 @@ import java.util.List;
|
||||||
public class UserTotpTest extends AbstractTestRealmKeycloakTest {
|
public class UserTotpTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
private static final UriBuilder BASE = UriBuilder.fromUri("http://localhost:8180/auth");
|
private static final UriBuilder BASE = UriBuilder.fromUri("http://localhost:8180/auth");
|
||||||
public static String ACCOUNT_REDIRECT = AccountService.loginRedirectUrl(BASE.clone()).build("test").toString();
|
public static String ACCOUNT_REDIRECT = AccountFormService.loginRedirectUrl(BASE.clone()).build("test").toString();
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public AssertEvents events = new AssertEvents(this);
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.account.AccountTest;
|
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
|
|
@ -43,7 +43,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.account.AccountTest;
|
import org.keycloak.testsuite.account.AccountFormServiceTest;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||||
|
@ -530,7 +530,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
||||||
// Go to account mgmt applications page
|
// Go to account mgmt applications page
|
||||||
applicationsPage.open();
|
applicationsPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT + "?path=applications").assertEvent();
|
events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountFormServiceTest.ACCOUNT_REDIRECT + "?path=applications").assertEvent();
|
||||||
Assert.assertTrue(applicationsPage.isCurrent());
|
Assert.assertTrue(applicationsPage.isCurrent());
|
||||||
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
|
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
|
||||||
Assert.assertTrue(apps.containsKey("offline-client-2"));
|
Assert.assertTrue(apps.containsKey("offline-client-2"));
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.account;
|
package org.keycloak.testsuite.ssl;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
|
@ -358,6 +358,13 @@
|
||||||
],
|
],
|
||||||
"adminUrl": "http://localhost:8180/varnamedapp/base/admin",
|
"adminUrl": "http://localhost:8180/varnamedapp/base/admin",
|
||||||
"secret": "password"
|
"secret": "password"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clientId": "direct-grant",
|
||||||
|
"enabled": true,
|
||||||
|
"directAccessGrantsEnabled": true,
|
||||||
|
"secret": "password",
|
||||||
|
"webOrigins": [ "http://localtest.me:8180" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"roles" : {
|
"roles" : {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.testsuite.Constants;
|
import org.keycloak.testsuite.Constants;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
@ -70,6 +70,6 @@ public class AccountPasswordPage extends AbstractAccountPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
return AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build(this.realmName).toString();
|
return AccountFormService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build(this.realmName).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
import org.keycloak.services.resources.AccountService;
|
import org.keycloak.services.resources.account.AccountFormService;
|
||||||
import org.keycloak.testsuite.Constants;
|
import org.keycloak.testsuite.Constants;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
@ -28,7 +28,7 @@ import javax.ws.rs.core.UriBuilder;
|
||||||
*/
|
*/
|
||||||
public class AccountTotpPage extends AbstractAccountPage {
|
public class AccountTotpPage extends AbstractAccountPage {
|
||||||
|
|
||||||
private static String PATH = AccountService.totpUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
|
private static String PATH = AccountFormService.totpUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
|
||||||
|
|
||||||
@FindBy(id = "totpSecret")
|
@FindBy(id = "totpSecret")
|
||||||
private WebElement totpSecret;
|
private WebElement totpSecret;
|
||||||
|
|
|
@ -119,6 +119,7 @@ usernameExistsMessage=Username already exists.
|
||||||
emailExistsMessage=Email already exists.
|
emailExistsMessage=Email already exists.
|
||||||
|
|
||||||
readOnlyUserMessage=You can''t update your account as it is read only.
|
readOnlyUserMessage=You can''t update your account as it is read only.
|
||||||
|
readOnlyUsernameMessage=You can''t update your username as it is read only.
|
||||||
readOnlyPasswordMessage=You can''t update your password as your account is read only.
|
readOnlyPasswordMessage=You can''t update your password as your account is read only.
|
||||||
|
|
||||||
successTotpMessage=Mobile authenticator configured.
|
successTotpMessage=Mobile authenticator configured.
|
||||||
|
|
Loading…
Reference in a new issue