KEYCLOAK-75 Retrieve user profile
This commit is contained in:
parent
d33c8c53f7
commit
7f691b463c
8 changed files with 359 additions and 36 deletions
|
@ -52,24 +52,27 @@ public class AuthenticationManager {
|
||||||
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
||||||
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
|
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
|
||||||
String cookiePath = uri.getPath();
|
String cookiePath = uri.getPath();
|
||||||
return createLoginCookie(realm, user, cookieName, cookiePath);
|
return createLoginCookie(realm, user, null, cookieName, cookiePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
|
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
|
||||||
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
|
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
|
||||||
URI uri = SaasService.saasCookiePath(uriInfo).build();
|
URI uri = SaasService.saasCookiePath(uriInfo).build();
|
||||||
String cookiePath = uri.getPath();
|
String cookiePath = uri.getPath();
|
||||||
return createLoginCookie(realm, user, cookieName, cookiePath);
|
return createLoginCookie(realm, user, null, cookieName, cookiePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, URI uri) {
|
public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
|
||||||
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
|
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
|
||||||
String cookiePath = uri.getPath();
|
String cookiePath = uri.getPath();
|
||||||
return createLoginCookie(realm, user, cookieName, cookiePath);
|
return createLoginCookie(realm, user, client, cookieName, cookiePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
|
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath) {
|
||||||
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
|
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
|
||||||
|
if (client != null) {
|
||||||
|
identityToken.issuedFor(client.getLoginName());
|
||||||
|
}
|
||||||
String encoded = encodeToken(realm, identityToken);
|
String encoded = encodeToken(realm, identityToken);
|
||||||
boolean secureOnly = !realm.isSslNotRequired();
|
boolean secureOnly = !realm.isSslNotRequired();
|
||||||
logger.debug("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
|
logger.debug("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
|
||||||
|
@ -123,15 +126,17 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||||
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
||||||
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
||||||
|
return auth != null ? auth.getUser() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||||
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
|
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
|
||||||
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
||||||
|
return auth != null ? auth.getUser() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserModel authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
public Auth authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||||
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
|
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
|
||||||
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
||||||
}
|
}
|
||||||
|
@ -140,11 +145,19 @@ public class AuthenticationManager {
|
||||||
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
|
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
|
||||||
if (user != null) return user;
|
if (user != null) return user;
|
||||||
|
|
||||||
|
Auth auth = authenticateBearerToken(realm, headers);
|
||||||
|
return auth != null ? auth.getUser() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Auth authenticateAccountIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||||
|
Auth auth = authenticateAccountIdentityCookie(realm, uriInfo, headers);
|
||||||
|
if (auth != null) return auth;
|
||||||
|
|
||||||
return authenticateBearerToken(realm, headers);
|
return authenticateBearerToken(realm, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
|
protected Auth authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
|
||||||
Cookie cookie = headers.getCookies().get(cookieName);
|
Cookie cookie = headers.getCookies().get(cookieName);
|
||||||
if (cookie == null) {
|
if (cookie == null) {
|
||||||
logger.debug("authenticateCookie could not find cookie: {0}", cookieName);
|
logger.debug("authenticateCookie could not find cookie: {0}", cookieName);
|
||||||
|
@ -159,13 +172,28 @@ public class AuthenticationManager {
|
||||||
expireIdentityCookie(realm, uriInfo);
|
expireIdentityCookie(realm, uriInfo);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Auth auth = new Auth();
|
||||||
|
|
||||||
UserModel user = realm.getUser(token.getPrincipal());
|
UserModel user = realm.getUser(token.getPrincipal());
|
||||||
if (user == null || !user.isEnabled()) {
|
if (user == null || !user.isEnabled()) {
|
||||||
logger.debug("Unknown user in identity cookie");
|
logger.debug("Unknown user in identity cookie");
|
||||||
expireIdentityCookie(realm, uriInfo);
|
expireIdentityCookie(realm, uriInfo);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return user;
|
auth.setUser(user);
|
||||||
|
|
||||||
|
if (token.getIssuedFor() != null) {
|
||||||
|
UserModel client = realm.getUser(token.getIssuedFor());
|
||||||
|
if (client == null || !client.isEnabled()) {
|
||||||
|
logger.debug("Unknown client in identity cookie");
|
||||||
|
expireIdentityCookie(realm, uriInfo);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
auth.setClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth;
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
logger.debug("Failed to verify identity cookie", e);
|
logger.debug("Failed to verify identity cookie", e);
|
||||||
expireIdentityCookie(realm, uriInfo);
|
expireIdentityCookie(realm, uriInfo);
|
||||||
|
@ -173,11 +201,11 @@ public class AuthenticationManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserModel authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
|
public Auth authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
|
||||||
String tokenString = null;
|
String tokenString = null;
|
||||||
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
|
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
|
||||||
if (authHeader == null) {
|
if (authHeader == null) {
|
||||||
throw new NotAuthorizedException("Bearer");
|
return null;
|
||||||
} else {
|
} else {
|
||||||
String[] split = authHeader.trim().split("\\s+");
|
String[] split = authHeader.trim().split("\\s+");
|
||||||
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
|
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
|
||||||
|
@ -191,11 +219,24 @@ public class AuthenticationManager {
|
||||||
if (!token.isActive()) {
|
if (!token.isActive()) {
|
||||||
throw new NotAuthorizedException("token_expired");
|
throw new NotAuthorizedException("token_expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Auth auth = new Auth();
|
||||||
|
|
||||||
UserModel user = realm.getUser(token.getPrincipal());
|
UserModel user = realm.getUser(token.getPrincipal());
|
||||||
if (user == null || !user.isEnabled()) {
|
if (user == null || !user.isEnabled()) {
|
||||||
throw new NotAuthorizedException("invalid_user");
|
throw new NotAuthorizedException("invalid_user");
|
||||||
}
|
}
|
||||||
return user;
|
auth.setUser(user);
|
||||||
|
|
||||||
|
if (token.getIssuedFor() != null) {
|
||||||
|
UserModel client = realm.getUser(token.getIssuedFor());
|
||||||
|
if (client == null || !client.isEnabled()) {
|
||||||
|
throw new NotAuthorizedException("invalid_user");
|
||||||
|
}
|
||||||
|
auth.setClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth;
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
logger.error("Failed to verify token", e);
|
logger.error("Failed to verify token", e);
|
||||||
throw new NotAuthorizedException("invalid_token");
|
throw new NotAuthorizedException("invalid_token");
|
||||||
|
@ -267,4 +308,25 @@ public class AuthenticationManager {
|
||||||
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
|
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Auth {
|
||||||
|
private UserModel user;
|
||||||
|
private UserModel client;
|
||||||
|
|
||||||
|
public UserModel getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserModel getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUser(UserModel user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setClient(UserModel client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,9 +388,11 @@ public class RealmManager {
|
||||||
rep.setLastName(user.getLastName());
|
rep.setLastName(user.getLastName());
|
||||||
rep.setFirstName(user.getFirstName());
|
rep.setFirstName(user.getFirstName());
|
||||||
rep.setEmail(user.getEmail());
|
rep.setEmail(user.getEmail());
|
||||||
Map<String, String> attrs = new HashMap<String, String>();
|
if (user.getAttributes() != null && !user.getAttributes().isEmpty()) {
|
||||||
attrs.putAll(user.getAttributes());
|
Map<String, String> attrs = new HashMap<String, String>();
|
||||||
rep.setAttributes(attrs);
|
attrs.putAll(user.getAttributes());
|
||||||
|
rep.setAttributes(attrs);
|
||||||
|
}
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
|
@ -39,6 +40,7 @@ import org.keycloak.AbstractOAuthClient;
|
||||||
import org.keycloak.jaxrs.JaxrsOAuthClient;
|
import org.keycloak.jaxrs.JaxrsOAuthClient;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.email.EmailSender;
|
import org.keycloak.services.email.EmailSender;
|
||||||
import org.keycloak.services.managers.AccessCodeEntry;
|
import org.keycloak.services.managers.AccessCodeEntry;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -89,18 +91,32 @@ public class AccountService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response forwardToPage(String path, String template) {
|
private Response forwardToPage(String path, String template) {
|
||||||
UserModel user = getUser(false);
|
AuthenticationManager.Auth auth = getAuth(false);
|
||||||
if (user != null) {
|
if (auth != null) {
|
||||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template);
|
return Flows.forms(realm, request, uriInfo).setUser(auth.getUser()).forwardToForm(template);
|
||||||
} else {
|
} else {
|
||||||
return login(path);
|
return login(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("")
|
||||||
|
@OPTIONS
|
||||||
|
public Response accountPreflight() {
|
||||||
|
return Cors.add(request, Response.ok()).auth().preflight().build();
|
||||||
|
}
|
||||||
|
|
||||||
@Path("")
|
@Path("")
|
||||||
@GET
|
@GET
|
||||||
public Response accountPage() {
|
public Response accountPage() {
|
||||||
return forwardToPage(null, Pages.ACCOUNT);
|
List<MediaType> types = headers.getAcceptableMediaTypes();
|
||||||
|
if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
|
||||||
|
return forwardToPage(null, Pages.ACCOUNT);
|
||||||
|
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
|
||||||
|
AuthenticationManager.Auth auth = getAuth(true);
|
||||||
|
return Cors.add(request, Response.ok(RealmManager.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getClient()).build();
|
||||||
|
} else {
|
||||||
|
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("social")
|
@Path("social")
|
||||||
|
@ -131,8 +147,8 @@ public class AccountService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
|
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
|
AuthenticationManager.Auth auth = getAuth(true);
|
||||||
UserModel user = getUser(true);
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
String error = Validation.validateUpdateProfileForm(formData);
|
String error = Validation.validateUpdateProfileForm(formData);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
@ -150,7 +166,9 @@ public class AccountService {
|
||||||
@Path("totp-remove")
|
@Path("totp-remove")
|
||||||
@GET
|
@GET
|
||||||
public Response processTotpRemove() {
|
public Response processTotpRemove() {
|
||||||
UserModel user = getUser(true);
|
AuthenticationManager.Auth auth = getAuth(true);
|
||||||
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
user.setTotp(false);
|
user.setTotp(false);
|
||||||
return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS)
|
return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS)
|
||||||
.setUser(user).forwardToTotp();
|
.setUser(user).forwardToTotp();
|
||||||
|
@ -160,7 +178,8 @@ public class AccountService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
|
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
UserModel user = getUser(true);
|
AuthenticationManager.Auth auth = getAuth(true);
|
||||||
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
String totp = formData.getFirst("totp");
|
String totp = formData.getFirst("totp");
|
||||||
String totpSecret = formData.getFirst("totpSecret");
|
String totpSecret = formData.getFirst("totpSecret");
|
||||||
|
@ -187,7 +206,8 @@ public class AccountService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
|
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
UserModel user = getUser(true);
|
AuthenticationManager.Auth auth = getAuth(true);
|
||||||
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
||||||
|
|
||||||
|
@ -288,7 +308,7 @@ public class AccountService {
|
||||||
}
|
}
|
||||||
URI redirectUri = redirectBuilder.build(realm.getId());
|
URI redirectUri = redirectBuilder.build(realm.getId());
|
||||||
|
|
||||||
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
|
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
|
||||||
return Response.status(302).cookie(cookie).location(redirectUri).build();
|
return Response.status(302).cookie(cookie).location(redirectUri).build();
|
||||||
} finally {
|
} finally {
|
||||||
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
|
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
|
||||||
|
@ -318,11 +338,12 @@ public class AccountService {
|
||||||
return oauth.redirect(uriInfo, accountUri.toString(), path);
|
return oauth.redirect(uriInfo, accountUri.toString(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserModel getUser(boolean required) {
|
private AuthenticationManager.Auth getAuth(boolean required) {
|
||||||
UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers);
|
AuthenticationManager.Auth auth = authManager.authenticateAccountIdentity(realm, uriInfo, headers);
|
||||||
if (user == null && required) {
|
if (auth == null && required) {
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
return user;
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,37 @@
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||||
|
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class Cors {
|
public class Cors {
|
||||||
|
|
||||||
|
public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
|
||||||
|
public static final String DEFAULT_ALLOW_METHODS = "GET, OPTIONS";
|
||||||
|
|
||||||
|
public static final String ORIGIN = "Origin";
|
||||||
|
|
||||||
|
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
|
||||||
|
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
|
||||||
|
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
|
||||||
|
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
|
||||||
|
public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
|
||||||
|
|
||||||
private HttpRequest request;
|
private HttpRequest request;
|
||||||
private ResponseBuilder response;
|
private ResponseBuilder response;
|
||||||
private Set<String> allowedOrigins;
|
private Set<String> allowedOrigins;
|
||||||
|
private String[] allowedMethods;
|
||||||
|
|
||||||
|
private boolean preflight;
|
||||||
|
private boolean auth;
|
||||||
|
|
||||||
public Cors(HttpRequest request, ResponseBuilder response) {
|
public Cors(HttpRequest request, ResponseBuilder response) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
|
@ -25,18 +42,60 @@ public class Cors {
|
||||||
return new Cors(request, response);
|
return new Cors(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cors allowedOrigins(Set<String> allowedOrigins) {
|
public Cors preflight() {
|
||||||
this.allowedOrigins = allowedOrigins;
|
preflight = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cors auth() {
|
||||||
|
auth = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cors allowedOrigins(UserModel client) {
|
||||||
|
if (client != null) {
|
||||||
|
allowedOrigins = client.getWebOrigins();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cors allowedMethods(String... allowedMethods) {
|
||||||
|
this.allowedMethods = allowedMethods;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response build() {
|
public Response build() {
|
||||||
String origin = request.getHttpHeaders().getHeaderString("Origin");
|
String origin = request.getHttpHeaders().getHeaderString(ORIGIN);
|
||||||
if (origin == null || allowedOrigins == null || (!allowedOrigins.contains(origin))) {
|
if (origin == null) {
|
||||||
return response.build();
|
return response.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
response.header("Access-Control-Allow-Origin", origin);
|
if (!preflight && (allowedOrigins == null || !allowedOrigins.contains(origin))) {
|
||||||
|
return response.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
response.header(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||||
|
|
||||||
|
if (allowedMethods != null) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < allowedMethods.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append(allowedMethods[i]);
|
||||||
|
}
|
||||||
|
response.header(ACCESS_CONTROL_ALLOW_METHODS, sb.toString());
|
||||||
|
} else {
|
||||||
|
response.header(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.header(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(auth));
|
||||||
|
if (auth) {
|
||||||
|
response.header(ACCESS_CONTROL_ALLOW_HEADERS, "Authorization");
|
||||||
|
}
|
||||||
|
|
||||||
|
response.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
||||||
|
|
||||||
return response.build();
|
return response.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -413,7 +413,7 @@ public class TokenService {
|
||||||
logger.debug("accessRequest SUCCESS");
|
logger.debug("accessRequest SUCCESS");
|
||||||
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
|
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
|
||||||
|
|
||||||
return Cors.add(request, Response.ok(res)).allowedOrigins(client.getWebOrigins()).build();
|
return Cors.add(request, Response.ok(res)).allowedOrigins(client).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
|
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
|
||||||
|
|
|
@ -189,6 +189,10 @@
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
<artifactId>selenium-java</artifactId>
|
<artifactId>selenium-java</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-chrome-driver</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mongodb</groupId>
|
<groupId>org.mongodb</groupId>
|
||||||
<artifactId>mongo-java-driver</artifactId>
|
<artifactId>mongo-java-driver</artifactId>
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
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.impl.client.DefaultHttpClient;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.testsuite.Constants;
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ProfileTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
UserModel user = appRealm.getUser("test-user@localhost");
|
||||||
|
user.setFirstName("First");
|
||||||
|
user.setLastName("Last");
|
||||||
|
user.setAttribute("key1", "value1");
|
||||||
|
user.setAttribute("key2", "value2");
|
||||||
|
|
||||||
|
for (ApplicationModel app : appRealm.getApplications()) {
|
||||||
|
if (app.getName().equals("test-app")) {
|
||||||
|
app.getApplicationUser().addWebOrigin("http://localtest.me:8081");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected WebDriver driver;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AccountUpdateProfilePage profilePage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProfile() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get("code");
|
||||||
|
String token = oauth.doAccessTokenRequest(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"));
|
||||||
|
assertEquals("test-user@localhost", profile.getString("email"));
|
||||||
|
assertEquals("First", profile.getString("firstName"));
|
||||||
|
assertEquals("Last", profile.getString("lastName"));
|
||||||
|
|
||||||
|
JSONObject attributes = profile.getJSONObject("attributes");
|
||||||
|
assertEquals(2, attributes.length());
|
||||||
|
assertEquals("value1", attributes.getString("key1"));
|
||||||
|
assertEquals("value2", attributes.getString("key2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProfileCors() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get("code");
|
||||||
|
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
|
||||||
|
|
||||||
|
driver.navigate().to("http://localtest.me:8081/app");
|
||||||
|
|
||||||
|
String[] response = doGetProfileJs(token);
|
||||||
|
assertEquals("200", response[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProfileCorsInvalidOrigin() throws Exception {
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get("code");
|
||||||
|
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
|
||||||
|
|
||||||
|
driver.navigate().to("http://invalid.localtest.me:8081");
|
||||||
|
|
||||||
|
try {
|
||||||
|
doGetProfileJs(token);
|
||||||
|
fail("Expected failure");
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProfileCookieAuth() throws Exception {
|
||||||
|
profilePage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
String[] response = doGetProfileJs(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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI getAccountURI() {
|
||||||
|
return UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT + "/rest/realms/" + oauth.getRealm() + "/account").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 String[] doGetProfileJs(String token) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("var req = new XMLHttpRequest();\n");
|
||||||
|
sb.append("req.open('GET', '" + getAccountURI().toString() + "', false);\n");
|
||||||
|
if (token != null) {
|
||||||
|
sb.append("req.setRequestHeader('Authorization', 'Bearer " + token + "');\n");
|
||||||
|
}
|
||||||
|
sb.append("req.setRequestHeader('Accept', 'application/json');\n");
|
||||||
|
sb.append("req.send(null);\n");
|
||||||
|
sb.append("return req.status + '///' + req.responseText;\n");
|
||||||
|
|
||||||
|
JavascriptExecutor js = (JavascriptExecutor) driver;
|
||||||
|
String response = (String) js.executeScript(sb.toString());
|
||||||
|
return response.split("///");
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,7 @@ public class WebRule extends ExternalResource {
|
||||||
|
|
||||||
if (browser.equals("htmlunit")) {
|
if (browser.equals("htmlunit")) {
|
||||||
HtmlUnitDriver d = new HtmlUnitDriver();
|
HtmlUnitDriver d = new HtmlUnitDriver();
|
||||||
|
d.getWebClient().getOptions().setJavaScriptEnabled(true);
|
||||||
d.getWebClient().getOptions().setCssEnabled(false);
|
d.getWebClient().getOptions().setCssEnabled(false);
|
||||||
driver = d;
|
driver = d;
|
||||||
} else if (browser.equals("chrome")) {
|
} else if (browser.equals("chrome")) {
|
||||||
|
|
Loading…
Reference in a new issue