Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
07107220ae
11 changed files with 212 additions and 35 deletions
|
@ -21,12 +21,14 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class AbstractOAuthClient {
|
public class AbstractOAuthClient {
|
||||||
|
public static final String OAUTH_TOKEN_REQUEST_STATE = "OAuth_Token_Request_State";
|
||||||
protected String clientId;
|
protected String clientId;
|
||||||
protected String password;
|
protected String password;
|
||||||
protected KeyStore truststore;
|
protected KeyStore truststore;
|
||||||
protected String authUrl;
|
protected String authUrl;
|
||||||
protected String codeUrl;
|
protected String codeUrl;
|
||||||
protected String stateCookieName = "OAuth_Token_Request_State";
|
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
||||||
|
protected String stateCookiePath;
|
||||||
protected Client client;
|
protected Client client;
|
||||||
protected boolean isSecure;
|
protected boolean isSecure;
|
||||||
protected final AtomicLong counter = new AtomicLong();
|
protected final AtomicLong counter = new AtomicLong();
|
||||||
|
@ -35,6 +37,9 @@ public class AbstractOAuthClient {
|
||||||
return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
|
return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Client for obtaining access token from code
|
||||||
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
client = new ResteasyClientBuilder().trustStore(truststore)
|
client = new ResteasyClientBuilder().trustStore(truststore)
|
||||||
|
@ -44,6 +49,9 @@ public class AbstractOAuthClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* closes cllient
|
||||||
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
|
@ -76,6 +84,8 @@ public class AbstractOAuthClient {
|
||||||
return authUrl;
|
return authUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setAuthUrl(String authUrl) {
|
public void setAuthUrl(String authUrl) {
|
||||||
this.authUrl = authUrl;
|
this.authUrl = authUrl;
|
||||||
}
|
}
|
||||||
|
@ -96,6 +106,14 @@ public class AbstractOAuthClient {
|
||||||
this.stateCookieName = stateCookieName;
|
this.stateCookieName = stateCookieName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getStateCookiePath() {
|
||||||
|
return stateCookiePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateCookiePath(String stateCookiePath) {
|
||||||
|
this.stateCookiePath = stateCookiePath;
|
||||||
|
}
|
||||||
|
|
||||||
public Client getClient() {
|
public Client getClient() {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +146,6 @@ public class AbstractOAuthClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String stripOauthParametersFromRedirect(String uri) {
|
protected String stripOauthParametersFromRedirect(String uri) {
|
||||||
System.out.println("******************** redirect_uri: " + uri);
|
|
||||||
UriBuilder builder = UriBuilder.fromUri(uri)
|
UriBuilder builder = UriBuilder.fromUri(uri)
|
||||||
.replaceQueryParam("code", null)
|
.replaceQueryParam("code", null)
|
||||||
.replaceQueryParam("state", null);
|
.replaceQueryParam("state", null);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.jaxrs;
|
package org.keycloak.jaxrs;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.keycloak.AbstractOAuthClient;
|
import org.keycloak.AbstractOAuthClient;
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
|
@ -19,6 +20,7 @@ import java.net.URI;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
|
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
|
|
||||||
|
@ -27,26 +29,42 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
.queryParam("redirect_uri", redirectUri)
|
.queryParam("redirect_uri", redirectUri)
|
||||||
.queryParam("state", state)
|
.queryParam("state", state)
|
||||||
.build();
|
.build();
|
||||||
NewCookie cookie = new NewCookie(stateCookieName, state, uriInfo.getBaseUri().getPath(), null, null, -1, true);
|
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
|
||||||
|
logger.info("NewCookie: " + cookie.toString());
|
||||||
return Response.status(302)
|
return Response.status(302)
|
||||||
.location(url)
|
.location(url)
|
||||||
.cookie(cookie).build();
|
.cookie(cookie).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBearerToken(UriInfo uriInfo, HttpHeaders headers) throws BadRequestException, InternalServerErrorException {
|
public String getStateCookiePath(UriInfo uriInfo) {
|
||||||
String error = uriInfo.getQueryParameters().getFirst("error");
|
if (stateCookiePath != null) return stateCookiePath;
|
||||||
if (error != null) throw new BadRequestException(new Exception("OAuth error: " + error));
|
return uriInfo.getBaseUri().getPath();
|
||||||
Cookie stateCookie = headers.getCookies().get(stateCookieName);
|
|
||||||
if (stateCookie == null) throw new BadRequestException(new Exception("state cookie not set"));
|
|
||||||
;
|
|
||||||
|
|
||||||
String state = uriInfo.getQueryParameters().getFirst("state");
|
|
||||||
if (state == null) throw new BadRequestException(new Exception("state parameter was null"));
|
|
||||||
if (!state.equals(stateCookie.getValue())) {
|
|
||||||
throw new BadRequestException(new Exception("state parameter invalid"));
|
|
||||||
}
|
}
|
||||||
String code = uriInfo.getQueryParameters().getFirst("code");
|
|
||||||
|
public String getBearerToken(UriInfo uriInfo, HttpHeaders headers) throws BadRequestException, InternalServerErrorException {
|
||||||
|
String error = getError(uriInfo);
|
||||||
|
if (error != null) throw new BadRequestException(new Exception("OAuth error: " + error));
|
||||||
|
checkStateCookie(uriInfo, headers);
|
||||||
|
String code = getAccessCode(uriInfo);
|
||||||
if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
|
if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
|
||||||
return resolveBearerToken(uriInfo.getRequestUri().toString(), code);
|
return resolveBearerToken(uriInfo.getRequestUri().toString(), code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getError(UriInfo uriInfo) {
|
||||||
|
return uriInfo.getQueryParameters().getFirst("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessCode(UriInfo uriInfo) {
|
||||||
|
return uriInfo.getQueryParameters().getFirst("code");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
|
||||||
|
Cookie stateCookie = headers.getCookies().get(stateCookieName);
|
||||||
|
if (stateCookie == null) throw new BadRequestException("state cookie not set");
|
||||||
|
String state = uriInfo.getQueryParameters().getFirst("state");
|
||||||
|
if (state == null) throw new BadRequestException("state parameter was null");
|
||||||
|
if (!state.equals(stateCookie.getValue())) {
|
||||||
|
throw new BadRequestException("state parameter invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,12 +51,13 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
||||||
.queryParam("redirect_uri", redirectUri)
|
.queryParam("redirect_uri", redirectUri)
|
||||||
.queryParam("state", state)
|
.queryParam("state", state)
|
||||||
.build();
|
.build();
|
||||||
String cookiePath = request.getContextPath();
|
String stateCookiePath = this.stateCookiePath;
|
||||||
if (cookiePath.equals("")) cookiePath = "/";
|
if (stateCookiePath == null) stateCookiePath = request.getContextPath();
|
||||||
|
if (stateCookiePath.equals("")) stateCookiePath = "/";
|
||||||
|
|
||||||
Cookie cookie = new Cookie(stateCookieName, state);
|
Cookie cookie = new Cookie(stateCookieName, state);
|
||||||
cookie.setSecure(isSecure);
|
cookie.setSecure(isSecure);
|
||||||
cookie.setPath(cookiePath);
|
cookie.setPath(stateCookiePath);
|
||||||
response.addCookie(cookie);
|
response.addCookie(cookie);
|
||||||
response.sendRedirect(url.toString());
|
response.sendRedirect(url.toString());
|
||||||
}
|
}
|
||||||
|
|
8
forms/src/main/java/org/keycloak/forms/UrlBean.java
Normal file → Executable file
8
forms/src/main/java/org/keycloak/forms/UrlBean.java
Normal file → Executable file
|
@ -70,20 +70,12 @@ public class UrlBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLoginAction() {
|
public String getLoginAction() {
|
||||||
if (realm.isSaas()) {
|
|
||||||
return Urls.saasLoginAction(baseURI).toString();
|
|
||||||
} else {
|
|
||||||
return Urls.realmLoginAction(baseURI, realm.getId()).toString();
|
return Urls.realmLoginAction(baseURI, realm.getId()).toString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public String getLoginUrl() {
|
public String getLoginUrl() {
|
||||||
if (realm.isSaas()) {
|
|
||||||
return Urls.saasLoginPage(baseURI).toString();
|
|
||||||
} else {
|
|
||||||
return Urls.realmLoginPage(baseURI, realm.getId()).toString();
|
return Urls.realmLoginPage(baseURI, realm.getId()).toString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public String getPasswordUrl() {
|
public String getPasswordUrl() {
|
||||||
return Urls.accountPasswordPage(baseURI, realm.getId()).toString();
|
return Urls.accountPasswordPage(baseURI, realm.getId()).toString();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -23,6 +24,7 @@ public class AccessCodeEntry {
|
||||||
protected String redirectUri;
|
protected String redirectUri;
|
||||||
|
|
||||||
protected long expiration;
|
protected long expiration;
|
||||||
|
protected RealmModel realm;
|
||||||
protected SkeletonKeyToken token;
|
protected SkeletonKeyToken token;
|
||||||
protected UserModel user;
|
protected UserModel user;
|
||||||
protected Set<RequiredAction> requiredActions;
|
protected Set<RequiredAction> requiredActions;
|
||||||
|
@ -38,6 +40,14 @@ public class AccessCodeEntry {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealm(RealmModel realm) {
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ public class ApplianceBootstrap {
|
||||||
realm.addRequiredResourceCredential(CredentialRepresentation.PASSWORD);
|
realm.addRequiredResourceCredential(CredentialRepresentation.PASSWORD);
|
||||||
realm.setTokenLifespan(300);
|
realm.setTokenLifespan(300);
|
||||||
realm.setAccessCodeLifespan(60);
|
realm.setAccessCodeLifespan(60);
|
||||||
|
realm.setAccessCodeLifespanUserAction(300);
|
||||||
realm.setSslNotRequired(true);
|
realm.setSslNotRequired(true);
|
||||||
realm.setCookieLoginAllowed(true);
|
realm.setCookieLoginAllowed(true);
|
||||||
realm.setRegistrationAllowed(false);
|
realm.setRegistrationAllowed(false);
|
||||||
|
@ -49,7 +50,7 @@ public class ApplianceBootstrap {
|
||||||
password.setType(UserCredentialModel.PASSWORD);
|
password.setType(UserCredentialModel.PASSWORD);
|
||||||
password.setValue("admin");
|
password.setValue("admin");
|
||||||
realm.updateCredential(adminUser, password);
|
realm.updateCredential(adminUser, password);
|
||||||
//adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
adminUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
|
||||||
adminConsole.grantRole(adminUser, adminRole);
|
adminConsole.grantRole(adminUser, adminRole);
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class AuthenticationManager {
|
||||||
expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
|
expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void expireCookie(String cookieName, String path) {
|
public void expireCookie(String cookieName, String path) {
|
||||||
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
|
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
logger.info("can't expire identity cookie, no HttpResponse");
|
logger.info("can't expire identity cookie, no HttpResponse");
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.services.managers;
|
||||||
import org.jboss.resteasy.jose.Base64Url;
|
import org.jboss.resteasy.jose.Base64Url;
|
||||||
import org.jboss.resteasy.jose.jws.JWSBuilder;
|
import org.jboss.resteasy.jose.jws.JWSBuilder;
|
||||||
import org.jboss.resteasy.jwt.JsonSerialization;
|
import org.jboss.resteasy.jwt.JsonSerialization;
|
||||||
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.representations.SkeletonKeyScope;
|
import org.keycloak.representations.SkeletonKeyScope;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
|
@ -22,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class TokenManager {
|
public class TokenManager {
|
||||||
|
protected static final Logger logger = Logger.getLogger(TokenManager.class);
|
||||||
|
|
||||||
protected Map<String, AccessCodeEntry> accessCodeMap = new ConcurrentHashMap<String, AccessCodeEntry>();
|
protected Map<String, AccessCodeEntry> accessCodeMap = new ConcurrentHashMap<String, AccessCodeEntry>();
|
||||||
|
|
||||||
|
@ -86,6 +88,9 @@ public class TokenManager {
|
||||||
|
|
||||||
|
|
||||||
createToken(code, realm, client, user);
|
createToken(code, realm, client, user);
|
||||||
|
logger.info("tokenmanager: access code id: " + code.getId());
|
||||||
|
logger.info("accesscode setExpiration: " + (System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
||||||
|
code.setRealm(realm);
|
||||||
code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
||||||
code.setClient(client);
|
code.setClient(client);
|
||||||
code.setUser(user);
|
code.setUser(user);
|
||||||
|
|
|
@ -47,10 +47,9 @@ public class KeycloakApplication extends Application {
|
||||||
TokenManager tokenManager = new TokenManager();
|
TokenManager tokenManager = new TokenManager();
|
||||||
|
|
||||||
singletons.add(new RealmsResource(tokenManager));
|
singletons.add(new RealmsResource(tokenManager));
|
||||||
|
singletons.add(new SaasService(tokenManager));
|
||||||
singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
|
singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
|
||||||
classes.add(SkeletonKeyContextResolver.class);
|
classes.add(SkeletonKeyContextResolver.class);
|
||||||
classes.add(SaasService.class);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakSessionFactory createSessionFactory() {
|
protected KeycloakSessionFactory createSessionFactory() {
|
||||||
|
|
|
@ -23,6 +23,7 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.jose.jws.JWSInput;
|
import org.jboss.resteasy.jose.jws.JWSInput;
|
||||||
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
|
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
|
||||||
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -52,6 +53,7 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class RequiredActionsService {
|
public class RequiredActionsService {
|
||||||
|
protected static final Logger logger = Logger.getLogger(RequiredActionsService.class);
|
||||||
|
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
|
|
||||||
|
@ -134,10 +136,13 @@ public class RequiredActionsService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response updatePassword(final MultivaluedMap<String, String> formData) {
|
public Response updatePassword(final MultivaluedMap<String, String> formData) {
|
||||||
|
logger.info("updatePassword");
|
||||||
AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
|
AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
|
||||||
if (accessCode == null) {
|
if (accessCode == null) {
|
||||||
|
logger.info("updatePassword access code is null");
|
||||||
return forwardToErrorPage();
|
return forwardToErrorPage();
|
||||||
}
|
}
|
||||||
|
logger.info("updatePassword has access code");
|
||||||
|
|
||||||
UserModel user = getUser(accessCode);
|
UserModel user = getUser(accessCode);
|
||||||
|
|
||||||
|
@ -158,6 +163,8 @@ public class RequiredActionsService {
|
||||||
|
|
||||||
realm.updateCredential(user, credentials);
|
realm.updateCredential(user, credentials);
|
||||||
|
|
||||||
|
logger.info("updatePassword updated credential");
|
||||||
|
|
||||||
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
||||||
if (accessCode != null) {
|
if (accessCode != null) {
|
||||||
accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
|
accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
|
||||||
|
@ -257,6 +264,7 @@ public class RequiredActionsService {
|
||||||
private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
|
private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
|
||||||
String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
|
String code = uriInfo.getQueryParameters().getFirst(FormFlows.CODE);
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
|
logger.info("getAccessCodeEntry code as not in query param");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,24 +273,31 @@ public class RequiredActionsService {
|
||||||
try {
|
try {
|
||||||
verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
|
verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
|
logger.info("getAccessCodeEntry code failed verification");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!verifiedCode) {
|
if (!verifiedCode) {
|
||||||
|
logger.info("getAccessCodeEntry code failed verification2");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String key = input.readContent(String.class);
|
String key = input.readContent(String.class);
|
||||||
AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
|
AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
|
||||||
if (accessCodeEntry == null) {
|
if (accessCodeEntry == null) {
|
||||||
|
logger.info("getAccessCodeEntry access code entry null");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessCodeEntry.isExpired()) {
|
if (accessCodeEntry.isExpired()) {
|
||||||
|
logger.info("getAccessCodeEntry: access code id: " + accessCodeEntry.getId());
|
||||||
|
logger.info("getAccessCodeEntry access code entry expired: " + accessCodeEntry.getExpiration());
|
||||||
|
logger.info("getAccessCodeEntry current time: " + (System.currentTimeMillis() / 1000));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessCodeEntry.getRequiredActions() == null || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
|
if (accessCodeEntry.getRequiredActions() == null || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
|
||||||
|
logger.info("getAccessCodeEntry required actions null || entry does not contain required action: " + (accessCodeEntry.getRequiredActions() == null) + "|" + !accessCodeEntry.getRequiredActions().contains(requiredAction) );
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,6 +318,7 @@ public class RequiredActionsService {
|
||||||
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
|
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user)
|
||||||
.forwardToAction(requiredActions.iterator().next());
|
.forwardToAction(requiredActions.iterator().next());
|
||||||
} else {
|
} else {
|
||||||
|
logger.info("redirectOauth: redirecting to: " + accessCode.getRedirectUri());
|
||||||
accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
||||||
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
|
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
|
||||||
accessCode.getState(), accessCode.getRedirectUri());
|
accessCode.getState(), accessCode.getRedirectUri());
|
||||||
|
|
|
@ -1,21 +1,33 @@
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.jboss.resteasy.jose.jws.JWSInput;
|
||||||
|
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
|
||||||
import org.jboss.resteasy.logging.Logger;
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.jboss.resteasy.spi.HttpResponse;
|
import org.jboss.resteasy.spi.HttpResponse;
|
||||||
import org.jboss.resteasy.spi.NotImplementedYetException;
|
import org.jboss.resteasy.spi.NotImplementedYetException;
|
||||||
|
import org.keycloak.AbstractOAuthClient;
|
||||||
|
import org.keycloak.jaxrs.JaxrsOAuthClient;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
import org.keycloak.services.managers.AccessCodeEntry;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
|
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.admin.RealmsAdminResource;
|
import org.keycloak.services.resources.admin.RealmsAdminResource;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.container.ResourceContext;
|
import javax.ws.rs.container.ResourceContext;
|
||||||
import javax.ws.rs.core.*;
|
import javax.ws.rs.core.*;
|
||||||
|
import javax.ws.rs.ext.Providers;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -42,8 +54,17 @@ public class SaasService {
|
||||||
@Context
|
@Context
|
||||||
protected ResourceContext resourceContext;
|
protected ResourceContext resourceContext;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected Providers providers;
|
||||||
|
|
||||||
|
|
||||||
protected String adminPath = "/admin/index.html";
|
protected String adminPath = "/admin/index.html";
|
||||||
protected AuthenticationManager authManager = new AuthenticationManager();
|
protected AuthenticationManager authManager = new AuthenticationManager();
|
||||||
|
protected TokenManager tokenManager;
|
||||||
|
|
||||||
|
public SaasService(TokenManager tokenManager) {
|
||||||
|
this.tokenManager = tokenManager;
|
||||||
|
}
|
||||||
|
|
||||||
public static class WhoAmI {
|
public static class WhoAmI {
|
||||||
protected String userId;
|
protected String userId;
|
||||||
|
@ -168,7 +189,99 @@ public class SaasService {
|
||||||
RealmModel realm = getAdminstrationRealm(realmManager);
|
RealmModel realm = getAdminstrationRealm(realmManager);
|
||||||
authManager.expireSaasIdentityCookie(uriInfo);
|
authManager.expireSaasIdentityCookie(uriInfo);
|
||||||
|
|
||||||
return Flows.forms(realm, request, uriInfo).forwardToLogin();
|
JaxrsOAuthClient oauth = new JaxrsOAuthClient();
|
||||||
|
String authUrl = TokenService.loginPageUrl(uriInfo).build(Constants.ADMIN_REALM).toString();
|
||||||
|
logger.info("authUrl: " + authUrl);
|
||||||
|
oauth.setAuthUrl(authUrl);
|
||||||
|
oauth.setClientId(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
|
URI redirectUri = uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "loginRedirect").build();
|
||||||
|
logger.info("redirectUri: " + redirectUri.toString());
|
||||||
|
oauth.setStateCookiePath(redirectUri.getPath());
|
||||||
|
return oauth.redirect(uriInfo, redirectUri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("login-redirect")
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
public Response loginRedirect(@QueryParam("code") String code,
|
||||||
|
@QueryParam("state") String state,
|
||||||
|
@QueryParam("error") String error,
|
||||||
|
@Context HttpHeaders headers
|
||||||
|
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
logger.info("loginRedirect ********************** <---");
|
||||||
|
if (error != null) {
|
||||||
|
logger.debug("error from oauth");
|
||||||
|
throw new ForbiddenException("error");
|
||||||
|
}
|
||||||
|
RealmManager realmManager = new RealmManager(session);
|
||||||
|
RealmModel realm = getAdminstrationRealm(realmManager);
|
||||||
|
if (!realm.isEnabled()) {
|
||||||
|
logger.debug("realm not enabled");
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
ApplicationModel adminConsole = realm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
|
UserModel adminConsoleUser = adminConsole.getApplicationUser();
|
||||||
|
if (!adminConsole.isEnabled() || !adminConsoleUser.isEnabled()) {
|
||||||
|
logger.debug("admin app not enabled");
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == null) {
|
||||||
|
logger.debug("code not specified");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
if (state == null) {
|
||||||
|
logger.debug("state not specified");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
|
||||||
|
|
||||||
|
JWSInput input = new JWSInput(code, providers);
|
||||||
|
boolean verifiedCode = false;
|
||||||
|
try {
|
||||||
|
verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
logger.debug("Failed to verify signature", ignored);
|
||||||
|
}
|
||||||
|
if (!verifiedCode) {
|
||||||
|
logger.debug("unverified access code");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
String key = input.readContent(String.class);
|
||||||
|
AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
|
||||||
|
if (accessCode == null) {
|
||||||
|
logger.debug("bad access code");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
if (accessCode.isExpired()) {
|
||||||
|
logger.debug("access code expired");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
if (!accessCode.getToken().isActive()) {
|
||||||
|
logger.debug("access token expired");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
if (!accessCode.getRealm().getId().equals(realm.getId())) {
|
||||||
|
logger.debug("bad realm");
|
||||||
|
throw new BadRequestException();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!adminConsoleUser.getLoginName().equals(accessCode.getClient().getLoginName())) {
|
||||||
|
logger.debug("bad client");
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
if (!adminConsole.hasRole(accessCode.getUser(), Constants.ADMIN_CONSOLE_ADMIN_ROLE)) {
|
||||||
|
logger.debug("not allowed");
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
logger.info("loginRedirect SUCCESS");
|
||||||
|
NewCookie cookie = authManager.createSaasIdentityCookie(realm, accessCode.getUser(), uriInfo);
|
||||||
|
return Response.status(302).cookie(cookie).location(contextRoot(uriInfo).path(adminPath).build()).build();
|
||||||
|
} finally {
|
||||||
|
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("logout")
|
@Path("logout")
|
||||||
|
@ -178,8 +291,9 @@ public class SaasService {
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmManager realmManager = new RealmManager(session);
|
||||||
RealmModel realm = getAdminstrationRealm(realmManager);
|
RealmModel realm = getAdminstrationRealm(realmManager);
|
||||||
authManager.expireSaasIdentityCookie(uriInfo);
|
authManager.expireSaasIdentityCookie(uriInfo);
|
||||||
|
authManager.expireIdentityCookie(realm, uriInfo);
|
||||||
|
|
||||||
return Flows.forms(realm, request, uriInfo).forwardToLogin();
|
return Response.status(302).location(uriInfo.getBaseUriBuilder().path(SaasService.class).path(SaasService.class, "loginPage").build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("logout-cookie")
|
@Path("logout-cookie")
|
||||||
|
@ -199,6 +313,8 @@ public class SaasService {
|
||||||
RealmModel realm = getAdminstrationRealm(realmManager);
|
RealmModel realm = getAdminstrationRealm(realmManager);
|
||||||
if (realm == null)
|
if (realm == null)
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
|
ApplicationModel adminConsole = realm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
|
UserModel adminConsoleUser = adminConsole.getApplicationUser();
|
||||||
|
|
||||||
if (!realm.isEnabled()) {
|
if (!realm.isEnabled()) {
|
||||||
throw new NotImplementedYetException();
|
throw new NotImplementedYetException();
|
||||||
|
@ -208,6 +324,8 @@ public class SaasService {
|
||||||
|
|
||||||
AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
|
||||||
|
|
||||||
|
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
NewCookie cookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
|
NewCookie cookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
|
||||||
|
@ -216,7 +334,7 @@ public class SaasService {
|
||||||
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData)
|
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData)
|
||||||
.forwardToLogin();
|
.forwardToLogin();
|
||||||
case ACTIONS_REQUIRED:
|
case ACTIONS_REQUIRED:
|
||||||
return Flows.forms(realm, request, uriInfo).forwardToAction(user.getRequiredActions().iterator().next());
|
return oauth.processAccessCode(null, "n", contextRoot(uriInfo).path(adminPath).build().toString(), adminConsoleUser, user);
|
||||||
default:
|
default:
|
||||||
return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
|
return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData)
|
||||||
.forwardToLogin();
|
.forwardToLogin();
|
||||||
|
|
Loading…
Reference in a new issue