Merge pull request #712 from patriot1burke/master
login refactor phase 2, still more to do
This commit is contained in:
commit
6212403569
20 changed files with 498 additions and 443 deletions
|
@ -36,7 +36,7 @@ public interface LoginFormsProvider extends Provider {
|
||||||
|
|
||||||
public Response createCode();
|
public Response createCode();
|
||||||
|
|
||||||
public LoginFormsProvider setAccessCode(String accessCode);
|
public LoginFormsProvider setClientSessionCode(String accessCode);
|
||||||
|
|
||||||
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
|
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
|
||||||
|
|
||||||
|
|
|
@ -293,7 +293,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginFormsProvider setAccessCode(String accessCode) {
|
public LoginFormsProvider setClientSessionCode(String accessCode) {
|
||||||
this.accessCode = accessCode;
|
this.accessCode = accessCode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,6 @@
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-federation</artifactId>
|
|
||||||
<version>2.7.0-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk16</artifactId>
|
<artifactId>bcprov-jdk16</artifactId>
|
||||||
|
|
37
services/src/main/java/org/keycloak/protocol/LoginProtocol.java
Executable file
37
services/src/main/java/org/keycloak/protocol/LoginProtocol.java
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
package org.keycloak.protocol;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.protocol.oidc.OAuthFlows;
|
||||||
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface LoginProtocol extends Provider {
|
||||||
|
OAuthFlows setSession(KeycloakSession session);
|
||||||
|
|
||||||
|
OAuthFlows setRealm(RealmModel realm);
|
||||||
|
|
||||||
|
OAuthFlows setRequest(HttpRequest request);
|
||||||
|
|
||||||
|
OAuthFlows setUriInfo(UriInfo uriInfo);
|
||||||
|
|
||||||
|
OAuthFlows setClientConnection(ClientConnection clientConnection);
|
||||||
|
|
||||||
|
Response cancelLogin(ClientSessionModel clientSession);
|
||||||
|
Response invalidSessionError(ClientSessionModel clientSession);
|
||||||
|
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
|
||||||
|
Response consentDenied(ClientSessionModel clientSession);
|
||||||
|
}
|
10
services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
Executable file
10
services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
package org.keycloak.protocol;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface LoginProtocolFactory extends ProviderFactory<LoginProtocol> {
|
||||||
|
}
|
27
services/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
Executable file
27
services/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
package org.keycloak.protocol;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class LoginProtocolSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "login-protocol";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return LoginProtocol.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return LoginProtocolFactory.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
178
services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java
Executable file
178
services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java
Executable file
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source.
|
||||||
|
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||||
|
* as indicated by the @author tags. See the copyright.txt file in the
|
||||||
|
* distribution for a full listing of individual contributors.
|
||||||
|
*
|
||||||
|
* This is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this software; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||||
|
*/
|
||||||
|
package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Cookie;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class OAuthFlows implements LoginProtocol {
|
||||||
|
|
||||||
|
public static final String LOGIN_PAGE_PROTOCOL = "openid-connect";
|
||||||
|
public static final String STATE_PARAM = "state";
|
||||||
|
public static final String SCOPE_PARAM = "scope";
|
||||||
|
public static final String RESPONSE_TYPE_PARAM = "response_type";
|
||||||
|
public static final String REDIRECT_URI_PARAM = "redirect_uri";
|
||||||
|
public static final String CLIENT_ID_PARAM = "client_id";
|
||||||
|
public static final String PROMPT_PARAM = "prompt";
|
||||||
|
public static final String LOGIN_HINT_PARAM = "login_hint";
|
||||||
|
private static final Logger log = Logger.getLogger(OAuthFlows.class);
|
||||||
|
|
||||||
|
protected KeycloakSession session;
|
||||||
|
|
||||||
|
protected RealmModel realm;
|
||||||
|
|
||||||
|
protected HttpRequest request;
|
||||||
|
|
||||||
|
protected UriInfo uriInfo;
|
||||||
|
|
||||||
|
protected ClientConnection clientConnection;
|
||||||
|
|
||||||
|
public OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo,
|
||||||
|
ClientConnection clientConnection) {
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.request = request;
|
||||||
|
this.uriInfo = uriInfo;
|
||||||
|
this.clientConnection = clientConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OAuthFlows() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthFlows setSession(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthFlows setRealm(RealmModel realm) {
|
||||||
|
this.realm = realm;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthFlows setRequest(HttpRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthFlows setUriInfo(UriInfo uriInfo) {
|
||||||
|
this.uriInfo = uriInfo;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthFlows setClientConnection(ClientConnection clientConnection) {
|
||||||
|
this.clientConnection = clientConnection;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response cancelLogin(ClientSessionModel clientSession) {
|
||||||
|
String redirect = clientSession.getRedirectUri();
|
||||||
|
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
|
||||||
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
||||||
|
if (state != null) {
|
||||||
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
|
}
|
||||||
|
return Response.status(302).location(redirectUri.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
||||||
|
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||||
|
String redirect = clientSession.getRedirectUri();
|
||||||
|
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
|
||||||
|
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
||||||
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
|
||||||
|
log.debugv("redirectAccessCode: state: {0}", state);
|
||||||
|
if (state != null)
|
||||||
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
|
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||||
|
|
||||||
|
return location.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response consentDenied(ClientSessionModel clientSession) {
|
||||||
|
String redirect = clientSession.getRedirectUri();
|
||||||
|
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
|
||||||
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
||||||
|
if (state != null)
|
||||||
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
|
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||||
|
return location.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Response invalidSessionError(ClientSessionModel clientSession) {
|
||||||
|
String redirect = clientSession.getRedirectUri();
|
||||||
|
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
|
||||||
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
||||||
|
if (state != null) {
|
||||||
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
|
}
|
||||||
|
return Response.status(302).location(redirectUri.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
32
services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java
Executable file
32
services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class OpenIDConnectFactory implements LoginProtocolFactory {
|
||||||
|
@Override
|
||||||
|
public LoginProtocol create(KeycloakSession session) {
|
||||||
|
return new OAuthFlows().setSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "openid-connect";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,32 @@
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
|
@ -22,12 +34,14 @@ import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.NewCookie;
|
import javax.ws.rs.core.NewCookie;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stateless object that manages authentication
|
* Stateless object that manages authentication
|
||||||
|
@ -78,7 +92,7 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
|
public static AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
|
||||||
AccessToken token = new AccessToken();
|
AccessToken token = new AccessToken();
|
||||||
token.id(KeycloakModelUtils.generateId());
|
token.id(KeycloakModelUtils.generateId());
|
||||||
token.issuedNow();
|
token.issuedNow();
|
||||||
|
@ -93,7 +107,7 @@ public class AuthenticationManager {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
|
public static void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
|
||||||
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
||||||
AccessToken identityToken = createIdentityToken(realm, user, session);
|
AccessToken identityToken = createIdentityToken(realm, user, session);
|
||||||
String encoded = encodeToken(realm, identityToken);
|
String encoded = encodeToken(realm, identityToken);
|
||||||
|
@ -116,7 +130,7 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
|
public static void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||||
// remember me cookie should be persistent (hardcoded to 365 days for now)
|
// remember me cookie should be persistent (hardcoded to 365 days for now)
|
||||||
|
@ -124,7 +138,7 @@ public class AuthenticationManager {
|
||||||
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 31536000, secureOnly, true);
|
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 31536000, secureOnly, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String encodeToken(RealmModel realm, Object token) {
|
protected static String encodeToken(RealmModel realm, Object token) {
|
||||||
String encodedToken = new JWSBuilder()
|
String encodedToken = new JWSBuilder()
|
||||||
.jsonContent(token)
|
.jsonContent(token)
|
||||||
.rsa256(realm.getPrivateKey());
|
.rsa256(realm.getPrivateKey());
|
||||||
|
@ -180,6 +194,114 @@ public class AuthenticationManager {
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
|
||||||
|
ClientSessionModel clientSession,
|
||||||
|
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
|
||||||
|
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
||||||
|
if (sessionCookie != null) {
|
||||||
|
|
||||||
|
String[] split = sessionCookie.getValue().split("/");
|
||||||
|
if (split.length >= 3) {
|
||||||
|
String oldSessionId = split[2];
|
||||||
|
if (!oldSessionId.equals(userSession.getId())) {
|
||||||
|
UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId);
|
||||||
|
if (oldSession != null) {
|
||||||
|
logger.debugv("Removing old user session: session: {0}", oldSessionId);
|
||||||
|
session.sessions().removeUserSession(realm, oldSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh the cookies!
|
||||||
|
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||||
|
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
||||||
|
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
|
||||||
|
protocol.setRealm(realm)
|
||||||
|
.setRequest(request)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setClientConnection(clientConnection);
|
||||||
|
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
|
||||||
|
ClientConnection clientConnection,
|
||||||
|
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
||||||
|
RealmModel realm = clientSession.getRealm();
|
||||||
|
UserModel user = userSession.getUser();
|
||||||
|
isTotpConfigurationRequired(realm, user);
|
||||||
|
isEmailVerificationRequired(realm, user);
|
||||||
|
ClientModel client = clientSession.getClient();
|
||||||
|
|
||||||
|
boolean isResource = client instanceof ApplicationModel;
|
||||||
|
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
||||||
|
|
||||||
|
|
||||||
|
logger.debugv("processAccessCode: isResource: {0}", isResource);
|
||||||
|
logger.debugv("processAccessCode: go to oauth page?: {0}",
|
||||||
|
!isResource);
|
||||||
|
|
||||||
|
event.detail(Details.CODE_ID, clientSession.getId());
|
||||||
|
|
||||||
|
Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
|
||||||
|
if (!requiredActions.isEmpty()) {
|
||||||
|
UserModel.RequiredAction action = user.getRequiredActions().iterator().next();
|
||||||
|
accessCode.setRequiredAction(action);
|
||||||
|
|
||||||
|
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
|
||||||
|
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
|
||||||
|
String key = UUID.randomUUID().toString();
|
||||||
|
clientSession.setNote("key", key);
|
||||||
|
loginFormsProvider.setVerifyCode(key);
|
||||||
|
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginFormsProvider
|
||||||
|
.createResponse(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResource) {
|
||||||
|
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
|
||||||
|
|
||||||
|
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
|
||||||
|
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
|
||||||
|
for (RoleModel r : accessCode.getRequestedRoles()) {
|
||||||
|
if (r.getContainer() instanceof RealmModel) {
|
||||||
|
realmRoles.add(r);
|
||||||
|
} else {
|
||||||
|
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Flows.forms(session, realm, client, uriInfo)
|
||||||
|
.setClientSessionCode(accessCode.getCode())
|
||||||
|
.setAccessRequest(realmRoles, resourceRoles)
|
||||||
|
.setClient(client)
|
||||||
|
.createOAuthGrant();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.success();
|
||||||
|
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void isTotpConfigurationRequired(RealmModel realm, UserModel user) {
|
||||||
|
for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
|
||||||
|
if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {
|
||||||
|
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||||
|
logger.debug("User is required to configure totp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void isEmailVerificationRequired(RealmModel realm, UserModel user) {
|
||||||
|
if (realm.isVerifyEmail() && !user.isEmailVerified()) {
|
||||||
|
user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||||
|
logger.debug("User is required to verify email");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) {
|
protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) {
|
||||||
try {
|
try {
|
||||||
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
|
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package org.keycloak.services.protocol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class OpenIdConnectProtocol {
|
|
||||||
public static final String LOGIN_PAGE_PROTOCOL = "oidc_login_page";
|
|
||||||
|
|
||||||
public static final String STATE_PARAM = "state";
|
|
||||||
public static final String SCOPE_PARAM = "scope";
|
|
||||||
public static final String RESPONSE_TYPE_PARAM = "response_type";
|
|
||||||
public static final String REDIRECT_URI_PARAM = "redirect_uri";
|
|
||||||
public static final String CLIENT_ID_PARAM = "client_id";
|
|
||||||
public static final String PROMPT_PARAM = "prompt";
|
|
||||||
public static final String LOGIN_HINT_PARAM = "login_hint";
|
|
||||||
}
|
|
|
@ -25,7 +25,6 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.BadRequestException;
|
import org.jboss.resteasy.spi.BadRequestException;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.account.AccountPages;
|
import org.keycloak.account.AccountPages;
|
||||||
import org.keycloak.account.AccountProvider;
|
import org.keycloak.account.AccountProvider;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -56,8 +55,8 @@ import org.keycloak.services.managers.Auth;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
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.protocol.OpenIdConnectProtocol;
|
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
import org.keycloak.protocol.oidc.OAuthFlows;
|
||||||
import org.keycloak.services.resources.flows.OAuthRedirect;
|
import org.keycloak.services.resources.flows.OAuthRedirect;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
|
@ -658,7 +657,7 @@ public class AccountService {
|
||||||
ClientSessionModel clientSession = auth.getClientSession();
|
ClientSessionModel clientSession = auth.getClientSession();
|
||||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
clientSession.setRedirectUri(redirectUri);
|
clientSession.setRedirectUri(redirectUri);
|
||||||
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, UUID.randomUUID().toString());
|
clientSession.setNote(OAuthFlows.STATE_PARAM, UUID.randomUUID().toString());
|
||||||
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
|
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
|
||||||
return Flows.social(realm, uriInfo, clientConnection, provider)
|
return Flows.social(realm, uriInfo, clientConnection, provider)
|
||||||
.redirectToSocialProvider(clientSessionCode);
|
.redirectToSocialProvider(clientSessionCode);
|
||||||
|
|
|
@ -24,8 +24,6 @@ package org.keycloak.services.resources;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.events.Event;
|
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -42,13 +40,12 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.AccessCode;
|
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
@ -165,7 +162,7 @@ public class RequiredActionsService {
|
||||||
String error = Validation.validateUpdateProfileForm(formData);
|
String error = Validation.validateUpdateProfileForm(formData);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error)
|
return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.UPDATE_PROFILE);
|
.createResponse(RequiredAction.UPDATE_PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,11 +209,11 @@ public class RequiredActionsService {
|
||||||
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
|
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
|
||||||
if (Validation.isEmpty(totp)) {
|
if (Validation.isEmpty(totp)) {
|
||||||
return loginForms.setError(Messages.MISSING_TOTP)
|
return loginForms.setError(Messages.MISSING_TOTP)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.CONFIGURE_TOTP);
|
.createResponse(RequiredAction.CONFIGURE_TOTP);
|
||||||
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
|
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
|
||||||
return loginForms.setError(Messages.INVALID_TOTP)
|
return loginForms.setError(Messages.INVALID_TOTP)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.CONFIGURE_TOTP);
|
.createResponse(RequiredAction.CONFIGURE_TOTP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,11 +254,11 @@ public class RequiredActionsService {
|
||||||
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
|
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
|
||||||
if (Validation.isEmpty(passwordNew)) {
|
if (Validation.isEmpty(passwordNew)) {
|
||||||
return loginForms.setError(Messages.MISSING_PASSWORD)
|
return loginForms.setError(Messages.MISSING_PASSWORD)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
} else if (!passwordNew.equals(passwordConfirm)) {
|
} else if (!passwordNew.equals(passwordConfirm)) {
|
||||||
return loginForms.setError(Messages.NOTMATCH_PASSWORD)
|
return loginForms.setError(Messages.NOTMATCH_PASSWORD)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +266,7 @@ public class RequiredActionsService {
|
||||||
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
||||||
} catch (Exception ape) {
|
} catch (Exception ape) {
|
||||||
return loginForms.setError(ape.getMessage())
|
return loginForms.setError(ape.getMessage())
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +327,7 @@ public class RequiredActionsService {
|
||||||
initEvent(clientSession);
|
initEvent(clientSession);
|
||||||
|
|
||||||
return Flows.forms(session, realm, null, uriInfo)
|
return Flows.forms(session, realm, null, uriInfo)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.setVerifyCode(verifyCode)
|
.setVerifyCode(verifyCode)
|
||||||
.setUser(userSession.getUser())
|
.setUser(userSession.getUser())
|
||||||
.createResponse(RequiredAction.VERIFY_EMAIL);
|
.createResponse(RequiredAction.VERIFY_EMAIL);
|
||||||
|
@ -356,11 +353,11 @@ public class RequiredActionsService {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
|
||||||
}
|
}
|
||||||
return Flows.forms(session, realm, null, uriInfo)
|
return Flows.forms(session, realm, null, uriInfo)
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
} else {
|
} else {
|
||||||
return Flows.forms(session, realm, null, uriInfo)
|
return Flows.forms(session, realm, null, uriInfo)
|
||||||
.setAccessCode(code)
|
.setClientSessionCode(code)
|
||||||
.createPasswordReset();
|
.createPasswordReset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -433,7 +430,7 @@ public class RequiredActionsService {
|
||||||
} catch (EmailException e) {
|
} catch (EmailException e) {
|
||||||
logger.error("Failed to send password reset email", e);
|
logger.error("Failed to send password reset email", e);
|
||||||
return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError")
|
return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError")
|
||||||
.setAccessCode(accessCode.getCode())
|
.setClientSessionCode(accessCode.getCode())
|
||||||
.createErrorPage();
|
.createErrorPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -442,36 +439,7 @@ public class RequiredActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
|
private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
if (accessCode == null) {
|
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<RequiredAction> requiredActions = user.getRequiredActions();
|
|
||||||
if (!requiredActions.isEmpty()) {
|
|
||||||
accessCode.setRequiredAction(requiredActions.iterator().next());
|
|
||||||
return Flows.forms(session, realm, null, uriInfo)
|
|
||||||
.setAccessCode(accessCode.getCode())
|
|
||||||
.setUser(user)
|
|
||||||
.createResponse(requiredActions.iterator().next());
|
|
||||||
} else {
|
|
||||||
logger.debugv("Redirecting to: {0}", clientSession.getRedirectUri());
|
|
||||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
|
||||||
|
|
||||||
AuthenticationManager authManager = new AuthenticationManager();
|
|
||||||
|
|
||||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
|
||||||
AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
|
|
||||||
return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
|
|
||||||
.redirectError(clientSession.getClient(), "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
|
|
||||||
}
|
|
||||||
event.session(userSession);
|
|
||||||
|
|
||||||
event.success();
|
|
||||||
|
|
||||||
return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
|
|
||||||
.redirectAccessCode(accessCode,
|
|
||||||
userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initEvent(ClientSessionModel clientSession) {
|
private void initEvent(ClientSessionModel clientSession) {
|
||||||
|
|
|
@ -25,12 +25,10 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.models.AccountRoles;
|
import org.keycloak.models.AccountRoles;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
@ -48,7 +46,6 @@ import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.social.AuthCallback;
|
import org.keycloak.social.AuthCallback;
|
||||||
import org.keycloak.social.SocialAccessDeniedException;
|
import org.keycloak.social.SocialAccessDeniedException;
|
||||||
|
@ -63,7 +60,6 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Cookie;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -156,7 +152,7 @@ public class SocialResource {
|
||||||
} catch (SocialAccessDeniedException e) {
|
} catch (SocialAccessDeniedException e) {
|
||||||
event.error(Errors.REJECTED_BY_USER);
|
event.error(Errors.REJECTED_BY_USER);
|
||||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setAccessCode(clientCode.getCode()).setWarning("Access denied").createLogin();
|
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setClientSessionCode(clientCode.getCode()).setWarning("Access denied").createLogin();
|
||||||
} catch (SocialProviderException e) {
|
} catch (SocialProviderException e) {
|
||||||
logger.error("Failed to process social callback", e);
|
logger.error("Failed to process social callback", e);
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process social callback");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process social callback");
|
||||||
|
@ -230,8 +226,8 @@ public class SocialResource {
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
TokenManager.attachClientSession(userSession, clientSession);
|
TokenManager.attachClientSession(userSession, clientSession);
|
||||||
|
|
||||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, new AuthenticationManager(), tokenManager);
|
AuthenticationManager authManager = new AuthenticationManager();
|
||||||
Response response = oauth.processAccessCode(clientSession, userSession, event);
|
Response response = authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
if (session.getTransaction().isActive()) {
|
if (session.getTransaction().isActive()) {
|
||||||
session.getTransaction().commit();
|
session.getTransaction().commit();
|
||||||
}
|
}
|
||||||
|
@ -239,7 +235,7 @@ public class SocialResource {
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
// Assume email is the duplicate as there's nothing else atm
|
// Assume email is the duplicate as there's nothing else atm
|
||||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.setError("socialEmailExists")
|
.setError("socialEmailExists")
|
||||||
.createLogin();
|
.createLogin();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.RefreshToken;
|
import org.keycloak.representations.RefreshToken;
|
||||||
|
@ -40,12 +41,10 @@ import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
|
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
|
||||||
import org.keycloak.representations.PasswordToken;
|
import org.keycloak.representations.PasswordToken;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
import org.keycloak.protocol.oidc.OAuthFlows;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.util.Base64Url;
|
import org.keycloak.util.Base64Url;
|
||||||
|
@ -114,8 +113,6 @@ public class TokenService {
|
||||||
protected ResourceContext resourceContext;
|
protected ResourceContext resourceContext;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
|
|
||||||
|
|
||||||
public TokenService(RealmModel realm, TokenManager tokenManager, EventBuilder event, AuthenticationManager authManager) {
|
public TokenService(RealmModel realm, TokenManager tokenManager, EventBuilder event, AuthenticationManager authManager) {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.tokenManager = tokenManager;
|
this.tokenManager = tokenManager;
|
||||||
|
@ -492,7 +489,7 @@ public class TokenService {
|
||||||
clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
event.client(clientSession.getClient()).error(Errors.INVALID_USER_CREDENTIALS);
|
event.client(clientSession.getClient()).error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
|
return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createLogin();
|
.createLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +508,6 @@ public class TokenService {
|
||||||
event.detail(Details.REMEMBER_ME, "true");
|
event.detail(Details.REMEMBER_ME, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
|
||||||
|
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
|
@ -525,7 +521,12 @@ public class TokenService {
|
||||||
|
|
||||||
if (formData.containsKey("cancel")) {
|
if (formData.containsKey("cancel")) {
|
||||||
event.error(Errors.REJECTED_BY_USER);
|
event.error(Errors.REJECTED_BY_USER);
|
||||||
return oauth.redirectError(client, "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
|
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
|
||||||
|
protocol.setRealm(realm)
|
||||||
|
.setRequest(request)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setClientConnection(clientConnection);
|
||||||
|
return protocol.cancelLogin(clientSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
|
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
|
||||||
|
@ -547,20 +548,19 @@ public class TokenService {
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
|
||||||
TokenManager.attachClientSession(userSession, clientSession);
|
TokenManager.attachClientSession(userSession, clientSession);
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
|
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
return oauth.processAccessCode(clientSession, userSession, event);
|
|
||||||
case ACCOUNT_TEMPORARILY_DISABLED:
|
case ACCOUNT_TEMPORARILY_DISABLED:
|
||||||
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||||
return Flows.forms(this.session, realm, client, uriInfo)
|
return Flows.forms(this.session, realm, client, uriInfo)
|
||||||
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
|
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createLogin();
|
.createLogin();
|
||||||
case ACCOUNT_DISABLED:
|
case ACCOUNT_DISABLED:
|
||||||
event.error(Errors.USER_DISABLED);
|
event.error(Errors.USER_DISABLED);
|
||||||
return Flows.forms(this.session, realm, client, uriInfo)
|
return Flows.forms(this.session, realm, client, uriInfo)
|
||||||
.setError(Messages.ACCOUNT_DISABLED)
|
.setError(Messages.ACCOUNT_DISABLED)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.setFormData(formData).createLogin();
|
.setFormData(formData).createLogin();
|
||||||
case MISSING_TOTP:
|
case MISSING_TOTP:
|
||||||
formData.remove(CredentialRepresentation.PASSWORD);
|
formData.remove(CredentialRepresentation.PASSWORD);
|
||||||
|
@ -570,19 +570,19 @@ public class TokenService {
|
||||||
|
|
||||||
return Flows.forms(this.session, realm, client, uriInfo)
|
return Flows.forms(this.session, realm, client, uriInfo)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createLoginTotp();
|
.createLoginTotp();
|
||||||
case INVALID_USER:
|
case INVALID_USER:
|
||||||
event.error(Errors.USER_NOT_FOUND);
|
event.error(Errors.USER_NOT_FOUND);
|
||||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
|
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createLogin();
|
.createLogin();
|
||||||
default:
|
default:
|
||||||
event.error(Errors.INVALID_USER_CREDENTIALS);
|
event.error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
|
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createLogin();
|
.createLogin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -642,8 +642,6 @@ public class TokenService {
|
||||||
.detail(Details.EMAIL, email)
|
.detail(Details.EMAIL, email)
|
||||||
.detail(Details.REGISTER_METHOD, "form");
|
.detail(Details.REGISTER_METHOD, "form");
|
||||||
|
|
||||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
|
||||||
|
|
||||||
if (!realm.isEnabled()) {
|
if (!realm.isEnabled()) {
|
||||||
event.error(Errors.REALM_DISABLED);
|
event.error(Errors.REALM_DISABLED);
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||||
|
@ -676,7 +674,7 @@ public class TokenService {
|
||||||
return Flows.forms(session, realm, client, uriInfo)
|
return Flows.forms(session, realm, client, uriInfo)
|
||||||
.setError(error)
|
.setError(error)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createRegistration();
|
.createRegistration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -686,7 +684,7 @@ public class TokenService {
|
||||||
return Flows.forms(session, realm, client, uriInfo)
|
return Flows.forms(session, realm, client, uriInfo)
|
||||||
.setError(Messages.USERNAME_EXISTS)
|
.setError(Messages.USERNAME_EXISTS)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createRegistration();
|
.createRegistration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,7 +694,7 @@ public class TokenService {
|
||||||
return Flows.forms(session, realm, client, uriInfo)
|
return Flows.forms(session, realm, client, uriInfo)
|
||||||
.setError(Messages.EMAIL_EXISTS)
|
.setError(Messages.EMAIL_EXISTS)
|
||||||
.setFormData(formData)
|
.setFormData(formData)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createRegistration();
|
.createRegistration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,7 +725,7 @@ public class TokenService {
|
||||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
return Flows.forms(session, realm, client, uriInfo)
|
return Flows.forms(session, realm, client, uriInfo)
|
||||||
.setError(passwordUpdateError)
|
.setError(passwordUpdateError)
|
||||||
.setAccessCode(clientCode.getCode())
|
.setClientSessionCode(clientCode.getCode())
|
||||||
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -981,15 +979,15 @@ public class TokenService {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
||||||
}
|
}
|
||||||
clientSession = clientCode.getClientSession();
|
clientSession = clientCode.getClientSession();
|
||||||
if (!clientSession.getAuthMethod().equals(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL)) {
|
if (!clientSession.getAuthMethod().equals(OAuthFlows.LOGIN_PAGE_PROTOCOL)) {
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid protocol, please login again through your application.");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid protocol, please login again through your application.");
|
||||||
}
|
}
|
||||||
state = clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM);
|
state = clientSession.getNote(OAuthFlows.STATE_PARAM);
|
||||||
scopeParam = clientSession.getNote(OpenIdConnectProtocol.SCOPE_PARAM);
|
scopeParam = clientSession.getNote(OAuthFlows.SCOPE_PARAM);
|
||||||
responseType = clientSession.getNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM);
|
responseType = clientSession.getNote(OAuthFlows.RESPONSE_TYPE_PARAM);
|
||||||
loginHint = clientSession.getNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM);
|
loginHint = clientSession.getNote(OAuthFlows.LOGIN_HINT_PARAM);
|
||||||
prompt = clientSession.getNote(OpenIdConnectProtocol.PROMPT_PARAM);
|
prompt = clientSession.getNote(OAuthFlows.PROMPT_PARAM);
|
||||||
} else {
|
} else {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
event.error(Errors.STATE_PARAM_NOT_FOUND);
|
event.error(Errors.STATE_PARAM_NOT_FOUND);
|
||||||
|
@ -1020,14 +1018,14 @@ public class TokenService {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||||
}
|
}
|
||||||
clientSession = session.sessions().createClientSession(realm, client);
|
clientSession = session.sessions().createClientSession(realm, client);
|
||||||
clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
|
clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL);
|
||||||
clientSession.setRedirectUri(redirect);
|
clientSession.setRedirectUri(redirect);
|
||||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
|
clientSession.setNote(OAuthFlows.STATE_PARAM, state);
|
||||||
if (scopeParam != null) clientSession.setNote(OpenIdConnectProtocol.SCOPE_PARAM, scopeParam);
|
if (scopeParam != null) clientSession.setNote(OAuthFlows.SCOPE_PARAM, scopeParam);
|
||||||
if (responseType != null) clientSession.setNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM, responseType);
|
if (responseType != null) clientSession.setNote(OAuthFlows.RESPONSE_TYPE_PARAM, responseType);
|
||||||
if (loginHint != null) clientSession.setNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM, loginHint);
|
if (loginHint != null) clientSession.setNote(OAuthFlows.LOGIN_HINT_PARAM, loginHint);
|
||||||
if (prompt != null) clientSession.setNote(OpenIdConnectProtocol.PROMPT_PARAM, prompt);
|
if (prompt != null) clientSession.setNote(OAuthFlows.PROMPT_PARAM, prompt);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1051,13 +1049,13 @@ public class TokenService {
|
||||||
@Path("login")
|
@Path("login")
|
||||||
@GET
|
@GET
|
||||||
public Response loginPage(@QueryParam("code") String code,
|
public Response loginPage(@QueryParam("code") String code,
|
||||||
@QueryParam(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM) String responseType,
|
@QueryParam(OAuthFlows.RESPONSE_TYPE_PARAM) String responseType,
|
||||||
@QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
|
@QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirect,
|
||||||
@QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
|
@QueryParam(OAuthFlows.CLIENT_ID_PARAM) String clientId,
|
||||||
@QueryParam(OpenIdConnectProtocol.SCOPE_PARAM) String scopeParam,
|
@QueryParam(OAuthFlows.SCOPE_PARAM) String scopeParam,
|
||||||
@QueryParam(OpenIdConnectProtocol.STATE_PARAM) String state,
|
@QueryParam(OAuthFlows.STATE_PARAM) String state,
|
||||||
@QueryParam(OpenIdConnectProtocol.PROMPT_PARAM) String prompt,
|
@QueryParam(OAuthFlows.PROMPT_PARAM) String prompt,
|
||||||
@QueryParam(OpenIdConnectProtocol.LOGIN_HINT_PARAM) String loginHint) {
|
@QueryParam(OAuthFlows.LOGIN_HINT_PARAM) String loginHint) {
|
||||||
event.event(EventType.LOGIN);
|
event.event(EventType.LOGIN);
|
||||||
FrontPageInitializer pageInitializer = new FrontPageInitializer();
|
FrontPageInitializer pageInitializer = new FrontPageInitializer();
|
||||||
pageInitializer.code = code;
|
pageInitializer.code = code;
|
||||||
|
@ -1081,23 +1079,23 @@ public class TokenService {
|
||||||
loginHint = pageInitializer.loginHint;
|
loginHint = pageInitializer.loginHint;
|
||||||
|
|
||||||
|
|
||||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
|
||||||
|
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers);
|
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers);
|
||||||
if (authResult != null) {
|
if (authResult != null) {
|
||||||
UserModel user = authResult.getUser();
|
UserModel user = authResult.getUser();
|
||||||
UserSessionModel session = authResult.getSession();
|
UserSessionModel userSession = authResult.getSession();
|
||||||
TokenManager.attachClientSession(session, clientSession);
|
TokenManager.attachClientSession(userSession, clientSession);
|
||||||
event.user(user).session(session).detail(Details.AUTH_METHOD, "sso");
|
event.user(user).session(userSession).detail(Details.AUTH_METHOD, "sso");
|
||||||
return oauth.processAccessCode(clientSession, session, event);
|
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt != null && prompt.equals("none")) {
|
if (prompt != null && prompt.equals("none")) {
|
||||||
return oauth.redirectError(clientSession.getClient(), "access_denied", state, redirect);
|
OAuthFlows oauth = new OAuthFlows(session, realm, request, uriInfo, clientConnection);
|
||||||
|
return oauth.cancelLogin(clientSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||||
.setAccessCode(new ClientSessionCode(realm, clientSession).getCode());
|
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
|
||||||
|
|
||||||
String rememberMeUsername = null;
|
String rememberMeUsername = null;
|
||||||
if (realm.isRememberMe()) {
|
if (realm.isRememberMe()) {
|
||||||
|
@ -1136,11 +1134,11 @@ public class TokenService {
|
||||||
@Path("registrations")
|
@Path("registrations")
|
||||||
@GET
|
@GET
|
||||||
public Response registerPage(@QueryParam("code") String code,
|
public Response registerPage(@QueryParam("code") String code,
|
||||||
@QueryParam("response_type") String responseType,
|
@QueryParam(OAuthFlows.RESPONSE_TYPE_PARAM) String responseType,
|
||||||
@QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
|
@QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirect,
|
||||||
@QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
|
@QueryParam(OAuthFlows.CLIENT_ID_PARAM) String clientId,
|
||||||
@QueryParam("scope") String scopeParam,
|
@QueryParam(OAuthFlows.SCOPE_PARAM) String scopeParam,
|
||||||
@QueryParam("state") String state) {
|
@QueryParam(OAuthFlows.STATE_PARAM) String state) {
|
||||||
event.event(EventType.REGISTER);
|
event.event(EventType.REGISTER);
|
||||||
if (!realm.isRegistrationAllowed()) {
|
if (!realm.isRegistrationAllowed()) {
|
||||||
event.error(Errors.REGISTRATION_DISABLED);
|
event.error(Errors.REGISTRATION_DISABLED);
|
||||||
|
@ -1162,7 +1160,7 @@ public class TokenService {
|
||||||
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
|
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
|
||||||
|
|
||||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||||
.setAccessCode(new ClientSessionCode(realm, clientSession).getCode())
|
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
|
||||||
.createRegistration();
|
.createRegistration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1175,7 +1173,7 @@ public class TokenService {
|
||||||
@Path("logout")
|
@Path("logout")
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
public Response logout(final @QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirectUri) {
|
public Response logout(final @QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirectUri) {
|
||||||
event.event(EventType.LOGOUT);
|
event.event(EventType.LOGOUT);
|
||||||
if (redirectUri != null) {
|
if (redirectUri != null) {
|
||||||
event.detail(Details.REDIRECT_URI, redirectUri);
|
event.detail(Details.REDIRECT_URI, redirectUri);
|
||||||
|
@ -1189,7 +1187,6 @@ public class TokenService {
|
||||||
if (redirectUri != null) {
|
if (redirectUri != null) {
|
||||||
String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm);
|
String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm);
|
||||||
if (validatedRedirect == null) {
|
if (validatedRedirect == null) {
|
||||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
|
||||||
}
|
}
|
||||||
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
|
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
|
||||||
|
@ -1267,7 +1264,6 @@ public class TokenService {
|
||||||
public Response processOAuth(final MultivaluedMap<String, String> formData) {
|
public Response processOAuth(final MultivaluedMap<String, String> formData) {
|
||||||
event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
|
event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
|
||||||
|
|
||||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
|
||||||
|
|
||||||
if (!checkSsl()) {
|
if (!checkSsl()) {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||||
|
@ -1306,15 +1302,19 @@ public class TokenService {
|
||||||
}
|
}
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
|
|
||||||
|
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
|
||||||
|
protocol.setRealm(realm)
|
||||||
|
.setRequest(request)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setClientConnection(clientConnection);
|
||||||
if (formData.containsKey("cancel")) {
|
if (formData.containsKey("cancel")) {
|
||||||
event.error(Errors.REJECTED_BY_USER);
|
event.error(Errors.REJECTED_BY_USER);
|
||||||
return redirectAccessDenied(redirect, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM));
|
return protocol.consentDenied(clientSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.success();
|
event.success();
|
||||||
|
|
||||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
|
||||||
return oauth.redirectAccessCode(accessCode, userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), redirect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("oauth/oob")
|
@Path("oauth/oob")
|
||||||
|
@ -1322,20 +1322,12 @@ public class TokenService {
|
||||||
public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
|
public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
|
||||||
LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo);
|
LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo);
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
return forms.setAccessCode(code).createCode();
|
return forms.setClientSessionCode(code).createCode();
|
||||||
} else {
|
} else {
|
||||||
return forms.setError(error).createCode();
|
return forms.setError(error).createCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response redirectAccessDenied(String redirect, String state) {
|
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
|
||||||
if (state != null)
|
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
|
||||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
|
||||||
return location.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
|
public static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
|
||||||
for (String validRedirect : validRedirects) {
|
for (String validRedirect : validRedirects) {
|
||||||
if (validRedirect.endsWith("*")) {
|
if (validRedirect.endsWith("*")) {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import org.keycloak.models.SocialLinkModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.representations.adapters.action.UserStats;
|
import org.keycloak.representations.adapters.action.UserStats;
|
||||||
|
@ -31,14 +30,13 @@ import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.SocialLinkRepresentation;
|
import org.keycloak.representations.idm.SocialLinkRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
import org.keycloak.services.managers.AccessCode;
|
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.managers.UserManager;
|
import org.keycloak.services.managers.UserManager;
|
||||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
import org.keycloak.protocol.oidc.OAuthFlows;
|
||||||
import org.keycloak.services.resources.flows.Urls;
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -897,7 +895,7 @@ public class UsersResource {
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
|
||||||
//audit.session(userSession);
|
//audit.session(userSession);
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||||
clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
|
clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL);
|
||||||
clientSession.setRedirectUri(redirect);
|
clientSession.setRedirectUri(redirect);
|
||||||
clientSession.setUserSession(userSession);
|
clientSession.setUserSession(userSession);
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
||||||
|
|
|
@ -21,14 +21,11 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.resources.flows;
|
package org.keycloak.services.resources.flows;
|
||||||
|
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
|
||||||
import org.keycloak.services.managers.TokenManager;
|
|
||||||
import org.keycloak.social.SocialProvider;
|
import org.keycloak.social.SocialProvider;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -46,11 +43,6 @@ public class Flows {
|
||||||
return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client);
|
return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OAuthFlows oauth(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager,
|
|
||||||
TokenManager tokenManager) {
|
|
||||||
return new OAuthFlows(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SocialRedirectFlows social(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, SocialProvider provider) {
|
public static SocialRedirectFlows social(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, SocialProvider provider) {
|
||||||
return new SocialRedirectFlows(realm, uriInfo, clientConnection, provider);
|
return new SocialRedirectFlows(realm, uriInfo, clientConnection, provider);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,276 +0,0 @@
|
||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source.
|
|
||||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
|
||||||
* as indicated by the @author tags. See the copyright.txt file in the
|
|
||||||
* distribution for a full listing of individual contributors.
|
|
||||||
*
|
|
||||||
* This is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as
|
|
||||||
* published by the Free Software Foundation; either version 2.1 of
|
|
||||||
* the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this software; if not, write to the Free
|
|
||||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
||||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
|
||||||
*/
|
|
||||||
package org.keycloak.services.resources.flows;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
|
||||||
import org.keycloak.ClientConnection;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.events.EventBuilder;
|
|
||||||
import org.keycloak.events.Details;
|
|
||||||
import org.keycloak.events.EventType;
|
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
|
||||||
import org.keycloak.models.ApplicationModel;
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.ClientSessionModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
|
||||||
import org.keycloak.services.managers.AccessCode;
|
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
|
||||||
import org.keycloak.services.managers.TokenManager;
|
|
||||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.Cookie;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class OAuthFlows {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(OAuthFlows.class);
|
|
||||||
|
|
||||||
private final KeycloakSession session;
|
|
||||||
|
|
||||||
private final RealmModel realm;
|
|
||||||
|
|
||||||
private final HttpRequest request;
|
|
||||||
|
|
||||||
private final UriInfo uriInfo;
|
|
||||||
|
|
||||||
private ClientConnection clientConnection;
|
|
||||||
private final AuthenticationManager authManager;
|
|
||||||
|
|
||||||
private final TokenManager tokenManager;
|
|
||||||
|
|
||||||
OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager,
|
|
||||||
TokenManager tokenManager) {
|
|
||||||
this.session = session;
|
|
||||||
this.realm = realm;
|
|
||||||
this.request = request;
|
|
||||||
this.uriInfo = uriInfo;
|
|
||||||
this.clientConnection = clientConnection;
|
|
||||||
this.authManager = authManager;
|
|
||||||
this.tokenManager = tokenManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response redirectAccessCode(ClientSessionCode accessCode, UserSessionModel userSession, String state, String redirect) {
|
|
||||||
String code = accessCode.getCode();
|
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
|
|
||||||
log.debugv("redirectAccessCode: state: {0}", state);
|
|
||||||
if (state != null)
|
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
|
||||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
|
||||||
|
|
||||||
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
|
||||||
if (sessionCookie != null) {
|
|
||||||
|
|
||||||
String[] split = sessionCookie.getValue().split("/");
|
|
||||||
if (split.length >= 3) {
|
|
||||||
String oldSessionId = split[2];
|
|
||||||
if (!oldSessionId.equals(userSession.getId())) {
|
|
||||||
UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId);
|
|
||||||
if (oldSession != null) {
|
|
||||||
log.debugv("Removing old user session: session: {0}", oldSessionId);
|
|
||||||
session.sessions().removeUserSession(realm, oldSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh the cookies!
|
|
||||||
authManager.createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
|
||||||
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
|
||||||
return location.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response redirectError(ClientModel client, String error, String state, String redirect) {
|
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
|
|
||||||
if (state != null) {
|
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
|
||||||
}
|
|
||||||
return Response.status(302).location(redirectUri.build()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Response processAccessCode(ClientSessionModel clientSession, UserSessionModel session, EventBuilder event) {
|
|
||||||
UserModel user = session.getUser();
|
|
||||||
isTotpConfigurationRequired(user);
|
|
||||||
isEmailVerificationRequired(user);
|
|
||||||
ClientModel client = clientSession.getClient();
|
|
||||||
|
|
||||||
boolean isResource = client instanceof ApplicationModel;
|
|
||||||
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
|
||||||
|
|
||||||
|
|
||||||
log.debugv("processAccessCode: isResource: {0}", isResource);
|
|
||||||
log.debugv("processAccessCode: go to oauth page?: {0}",
|
|
||||||
!isResource);
|
|
||||||
|
|
||||||
event.detail(Details.CODE_ID, clientSession.getId());
|
|
||||||
|
|
||||||
Set<RequiredAction> requiredActions = user.getRequiredActions();
|
|
||||||
if (!requiredActions.isEmpty()) {
|
|
||||||
RequiredAction action = user.getRequiredActions().iterator().next();
|
|
||||||
accessCode.setRequiredAction(action);
|
|
||||||
|
|
||||||
LoginFormsProvider loginFormsProvider = Flows.forms(this.session, realm, client, uriInfo).setAccessCode(accessCode.getCode()).setUser(user);
|
|
||||||
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
|
|
||||||
String key = UUID.randomUUID().toString();
|
|
||||||
clientSession.setNote("key", key);
|
|
||||||
loginFormsProvider.setVerifyCode(key);
|
|
||||||
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
|
||||||
}
|
|
||||||
|
|
||||||
return loginFormsProvider
|
|
||||||
.createResponse(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isResource) {
|
|
||||||
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
|
|
||||||
|
|
||||||
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
|
|
||||||
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
|
|
||||||
for (RoleModel r : accessCode.getRequestedRoles()) {
|
|
||||||
if (r.getContainer() instanceof RealmModel) {
|
|
||||||
realmRoles.add(r);
|
|
||||||
} else {
|
|
||||||
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Flows.forms(this.session, realm, client, uriInfo)
|
|
||||||
.setAccessCode(accessCode.getCode())
|
|
||||||
.setAccessRequest(realmRoles, resourceRoles)
|
|
||||||
.setClient(client)
|
|
||||||
.createOAuthGrant();
|
|
||||||
}
|
|
||||||
|
|
||||||
String redirect = clientSession.getRedirectUri();
|
|
||||||
|
|
||||||
if (redirect != null) {
|
|
||||||
event.success();
|
|
||||||
String state = clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM);
|
|
||||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
|
||||||
return redirectAccessCode(accessCode, session, state, redirect);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, EventBuilder event) {
|
|
||||||
isTotpConfigurationRequired(user);
|
|
||||||
isEmailVerificationRequired(user);
|
|
||||||
|
|
||||||
boolean isResource = client instanceof ApplicationModel;
|
|
||||||
AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session);
|
|
||||||
|
|
||||||
log.debugv("processAccessCode: isResource: {0}", isResource);
|
|
||||||
log.debugv("processAccessCode: go to oauth page?: {0}",
|
|
||||||
!isResource);
|
|
||||||
|
|
||||||
event.detail(Details.CODE_ID, accessCode.getCodeId());
|
|
||||||
|
|
||||||
Set<RequiredAction> requiredActions = user.getRequiredActions();
|
|
||||||
if (!requiredActions.isEmpty()) {
|
|
||||||
RequiredAction action = user.getRequiredActions().iterator().next();
|
|
||||||
accessCode.setRequiredAction(action);
|
|
||||||
|
|
||||||
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
|
|
||||||
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Flows.forms(this.session, realm, client, uriInfo).setAccessCode(accessCode.getCode()).setUser(user)
|
|
||||||
.createResponse(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isResource) {
|
|
||||||
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
|
|
||||||
|
|
||||||
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
|
|
||||||
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
|
|
||||||
for (RoleModel r : accessCode.getRequestedRoles()) {
|
|
||||||
if (r.getContainer() instanceof RealmModel) {
|
|
||||||
realmRoles.add(r);
|
|
||||||
} else {
|
|
||||||
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Flows.forms(this.session, realm, client, uriInfo)
|
|
||||||
.setAccessCode(accessCode.getCode())
|
|
||||||
.setAccessRequest(realmRoles, resourceRoles)
|
|
||||||
.setClient(client)
|
|
||||||
.createOAuthGrant();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirect != null) {
|
|
||||||
event.success();
|
|
||||||
|
|
||||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
|
||||||
return redirectAccessCode(accessCode, session, state, redirect);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
public Response forwardToSecurityFailure(String message) {
|
|
||||||
return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
private void isTotpConfigurationRequired(UserModel user) {
|
|
||||||
for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
|
|
||||||
if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {
|
|
||||||
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
|
||||||
log.debug("User is required to configure totp");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void isEmailVerificationRequired(UserModel user) {
|
|
||||||
if (realm.isVerifyEmail() && !user.isEmailVerified()) {
|
|
||||||
user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
|
|
||||||
log.debug("User is required to verify email");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.protocol.oidc.OpenIDConnectFactory
|
1
services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
Executable file
1
services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
Executable file
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.protocol.LoginProtocolSpi
|
|
@ -10,7 +10,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.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
import org.keycloak.protocol.oidc.OAuthFlows;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ public class UserSessionProviderTest {
|
||||||
assertEquals(realm.findClient("test-app").getClientId(), session.getClient().getClientId());
|
assertEquals(realm.findClient("test-app").getClientId(), session.getClient().getClientId());
|
||||||
assertEquals(sessions[0].getId(), session.getUserSession().getId());
|
assertEquals(sessions[0].getId(), session.getUserSession().getId());
|
||||||
assertEquals("http://redirect", session.getRedirectUri());
|
assertEquals("http://redirect", session.getRedirectUri());
|
||||||
assertEquals("state", session.getNote(OpenIdConnectProtocol.STATE_PARAM));
|
assertEquals("state", session.getNote(OAuthFlows.STATE_PARAM));
|
||||||
assertEquals(2, session.getRoles().size());
|
assertEquals(2, session.getRoles().size());
|
||||||
assertTrue(session.getRoles().contains("one"));
|
assertTrue(session.getRoles().contains("one"));
|
||||||
assertTrue(session.getRoles().contains("two"));
|
assertTrue(session.getRoles().contains("two"));
|
||||||
|
@ -250,7 +250,7 @@ public class UserSessionProviderTest {
|
||||||
clientSession.setUserSession(userSession);
|
clientSession.setUserSession(userSession);
|
||||||
clientSession.setRedirectUri("http://redirect");
|
clientSession.setRedirectUri("http://redirect");
|
||||||
clientSession.setRoles(new HashSet<String>());
|
clientSession.setRoles(new HashSet<String>());
|
||||||
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, "state");
|
clientSession.setNote(OAuthFlows.STATE_PARAM, "state");
|
||||||
}
|
}
|
||||||
|
|
||||||
resetSession();
|
resetSession();
|
||||||
|
@ -289,7 +289,7 @@ public class UserSessionProviderTest {
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||||
if (userSession != null) clientSession.setUserSession(userSession);
|
if (userSession != null) clientSession.setUserSession(userSession);
|
||||||
clientSession.setRedirectUri(redirect);
|
clientSession.setRedirectUri(redirect);
|
||||||
if (state != null) clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
|
if (state != null) clientSession.setNote(OAuthFlows.STATE_PARAM, state);
|
||||||
if (roles != null) clientSession.setRoles(roles);
|
if (roles != null) clientSession.setRoles(roles);
|
||||||
return clientSession;
|
return clientSession;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue