ClientSession everywhere refactor phase1
This commit is contained in:
parent
0bf6c36ca7
commit
524b423733
55 changed files with 1330 additions and 667 deletions
|
@ -35,6 +35,8 @@ public interface Errors {
|
|||
|
||||
String SOCIAL_PROVIDER_NOT_FOUND = "social_provider_not_found";
|
||||
String SOCIAL_ID_IN_USE = "social_id_in_use";
|
||||
String STATE_PARAM_NOT_FOUND = "state_param_not_found";
|
||||
String SSL_REQUIRED = "ssl_required";
|
||||
|
||||
String USER_NOT_LOGGED_IN = "user_not_logged_in";
|
||||
String USER_SESSION_NOT_FOUND = "user_session_not_found";
|
||||
|
|
|
@ -39,6 +39,8 @@ public enum EventType {
|
|||
SEND_VERIFY_EMAIL,
|
||||
SEND_VERIFY_EMAIL_ERROR,
|
||||
SEND_RESET_PASSWORD,
|
||||
SEND_RESET_PASSWORD_ERROR
|
||||
SEND_RESET_PASSWORD_ERROR,
|
||||
SOCIAL_LOGIN,
|
||||
SOCIAL_LOGIN_ERROR
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ public interface LoginFormsProvider extends Provider {
|
|||
|
||||
public LoginFormsProvider setClient(ClientModel client);
|
||||
|
||||
LoginFormsProvider setVerifyCode(String code);
|
||||
|
||||
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams);
|
||||
|
||||
public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.login.freemarker;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
|
@ -50,6 +51,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
|
||||
|
||||
private String verifyCode;
|
||||
private String message;
|
||||
private String accessCode;
|
||||
private Response.Status status = Response.Status.OK;
|
||||
|
@ -108,7 +110,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
case VERIFY_EMAIL:
|
||||
try {
|
||||
UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
|
||||
builder.queryParam("key", accessCode);
|
||||
builder.queryParam("code", accessCode);
|
||||
builder.queryParam("key", verifyCode);
|
||||
|
||||
String link = builder.build(realm.getName()).toString();
|
||||
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
||||
|
@ -134,7 +137,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
}
|
||||
|
||||
private Response createResponse(LoginFormsPages page) {
|
||||
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : uriInfo.getQueryParameters();
|
||||
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
|
||||
|
||||
String requestURI = uriInfo.getBaseUri().getPath();
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
|
||||
|
@ -308,6 +311,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setVerifyCode(String code) {
|
||||
this.verifyCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams) {
|
||||
this.queryParams = queryParams;
|
||||
|
|
|
@ -8,14 +8,14 @@ import java.util.Set;
|
|||
public interface ClientSessionModel {
|
||||
|
||||
public String getId();
|
||||
|
||||
public RealmModel getRealm();
|
||||
public ClientModel getClient();
|
||||
|
||||
public String getState();
|
||||
|
||||
public UserSessionModel getUserSession();
|
||||
public void setUserSession(UserSessionModel userSession);
|
||||
|
||||
public String getRedirectUri();
|
||||
public void setRedirectUri(String uri);
|
||||
|
||||
public int getTimestamp();
|
||||
|
||||
|
@ -26,6 +26,7 @@ public interface ClientSessionModel {
|
|||
public void setAction(Action action);
|
||||
|
||||
public Set<String> getRoles();
|
||||
public void setRoles(Set<String> roles);
|
||||
|
||||
/**
|
||||
* Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc.
|
||||
|
@ -45,7 +46,9 @@ public interface ClientSessionModel {
|
|||
VERIFY_EMAIL,
|
||||
UPDATE_PROFILE,
|
||||
CONFIGURE_TOTP,
|
||||
UPDATE_PASSWORD
|
||||
UPDATE_PASSWORD,
|
||||
AUTHENTICATE,
|
||||
SOCIAL_CALLBACK
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,8 +11,9 @@ import java.util.Set;
|
|||
*/
|
||||
public interface UserSessionProvider extends Provider {
|
||||
|
||||
ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles);
|
||||
ClientSessionModel createClientSession(RealmModel realm, ClientModel client);
|
||||
ClientSessionModel getClientSession(RealmModel realm, String id);
|
||||
ClientSessionModel getClientSession(String id);
|
||||
|
||||
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe);
|
||||
UserSessionModel getUserSession(RealmModel realm, String id);
|
||||
|
|
|
@ -8,10 +8,13 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity;
|
||||
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -31,6 +34,11 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return session.realms().getRealm(entity.getRealmId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNote(String name, String value) {
|
||||
for (ClientSessionNoteEntity attr : entity.getNotes()) {
|
||||
|
@ -80,8 +88,32 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return entity.getState();
|
||||
public void setUserSession(UserSessionModel userSession) {
|
||||
UserSessionAdapter adapter = (UserSessionAdapter)userSession;
|
||||
UserSessionEntity userSessionEntity = adapter.getEntity();
|
||||
entity.setSession(userSessionEntity);
|
||||
userSessionEntity.getClientSessions().add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUri(String uri) {
|
||||
entity.setRedirectUri(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoles(Set<String> roles) {
|
||||
if (roles != null) {
|
||||
List<ClientSessionRoleEntity> roleEntities = new LinkedList<ClientSessionRoleEntity>();
|
||||
for (String r : roles) {
|
||||
ClientSessionRoleEntity roleEntity = new ClientSessionRoleEntity();
|
||||
roleEntity.setClientSession(entity);
|
||||
roleEntity.setRoleId(r);
|
||||
em.persist(roleEntity);
|
||||
|
||||
roleEntities.add(roleEntity);
|
||||
}
|
||||
entity.setRoles(roleEntities);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -96,6 +128,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
|
||||
@Override
|
||||
public UserSessionModel getUserSession() {
|
||||
if (entity.getSession() == null) return null;
|
||||
return new UserSessionAdapter(session, em, realm, entity.getSession());
|
||||
}
|
||||
|
||||
|
|
|
@ -37,33 +37,14 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles) {
|
||||
UserSessionEntity userSessionEntity = em.find(UserSessionEntity.class, userSession.getId());
|
||||
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
||||
ClientSessionEntity entity = new ClientSessionEntity();
|
||||
entity.setId(KeycloakModelUtils.generateId());
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setClientId(client.getId());
|
||||
entity.setSession(userSessionEntity);
|
||||
entity.setRedirectUri(redirectUri);
|
||||
entity.setState(state);
|
||||
entity.setRealmId(realm.getId());
|
||||
em.persist(entity);
|
||||
|
||||
if (roles != null) {
|
||||
List<ClientSessionRoleEntity> roleEntities = new LinkedList<ClientSessionRoleEntity>();
|
||||
for (String r : roles) {
|
||||
ClientSessionRoleEntity roleEntity = new ClientSessionRoleEntity();
|
||||
roleEntity.setClientSession(entity);
|
||||
roleEntity.setRoleId(r);
|
||||
em.persist(roleEntity);
|
||||
|
||||
roleEntities.add(roleEntity);
|
||||
}
|
||||
entity.setRoles(roleEntities);
|
||||
}
|
||||
|
||||
userSessionEntity.getClientSessions().add(entity);
|
||||
|
||||
return new ClientSessionAdapter(session, em, realm, entity);
|
||||
}
|
||||
|
||||
|
@ -76,6 +57,16 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(String id) {
|
||||
ClientSessionEntity clientSession = em.find(ClientSessionEntity.class, id);
|
||||
if (clientSession != null) {
|
||||
RealmModel realm = session.realms().getRealm(clientSession.getRealmId());
|
||||
return new ClientSessionAdapter(session, em, realm, clientSession);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
|
||||
String id = username + "-" + realm;
|
||||
|
@ -208,6 +199,18 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
|||
int maxTime = Time.currentTime() - realm.getSsoSessionMaxLifespan();
|
||||
int idleTime = Time.currentTime() - realm.getSsoSessionIdleTimeout();
|
||||
|
||||
em.createNamedQuery("removeDetachedClientSessionRoleByExpired")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("maxTime", idleTime)
|
||||
.executeUpdate();
|
||||
em.createNamedQuery("removeDetachedClientSessionNoteByExpired")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("maxTime", idleTime)
|
||||
.executeUpdate();
|
||||
em.createNamedQuery("removeDetachedClientSessionByExpired")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("maxTime", idleTime)
|
||||
.executeUpdate();
|
||||
em.createNamedQuery("removeClientSessionRoleByExpired")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("maxTime", maxTime)
|
||||
|
|
|
@ -23,10 +23,11 @@ import java.util.Collection;
|
|||
@Entity
|
||||
@Table(name = "CLIENT_SESSION")
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "removeClientSessionByRealm", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionByRealm", query = "delete from ClientSessionEntity a where a.realmId = :realmId"),
|
||||
@NamedQuery(name = "removeClientSessionByUser", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId)"),
|
||||
@NamedQuery(name = "removeClientSessionByClient", query = "delete from ClientSessionEntity a where a.clientId = :clientId and a.session IN (select s from UserSessionEntity s where s.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionByExpired", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime))")
|
||||
@NamedQuery(name = "removeClientSessionByClient", query = "delete from ClientSessionEntity a where a.clientId = :clientId and a.realmId = :realmId"),
|
||||
@NamedQuery(name = "removeClientSessionByExpired", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime))"),
|
||||
@NamedQuery(name = "removeDetachedClientSessionByExpired", query = "delete from ClientSessionEntity a where a.session IS NULL and a.timestamp < :maxTime and a.realmId = :realmId")
|
||||
})
|
||||
public class ClientSessionEntity {
|
||||
|
||||
|
@ -41,6 +42,9 @@ public class ClientSessionEntity {
|
|||
@Column(name="CLIENT_ID",length = 36)
|
||||
protected String clientId;
|
||||
|
||||
@Column(name="REALM_ID")
|
||||
protected String realmId;
|
||||
|
||||
@Column(name="TIMESTAMP")
|
||||
protected int timestamp;
|
||||
|
||||
|
@ -50,9 +54,6 @@ public class ClientSessionEntity {
|
|||
@Column(name="AUTH_METHOD")
|
||||
protected String authMethod;
|
||||
|
||||
@Column(name="STATE")
|
||||
protected String state;
|
||||
|
||||
@Column(name="ACTION")
|
||||
protected ClientSessionModel.Action action;
|
||||
|
||||
|
@ -86,6 +87,14 @@ public class ClientSessionEntity {
|
|||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
@ -102,14 +111,6 @@ public class ClientSessionEntity {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public ClientSessionModel.Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
|
|
@ -18,9 +18,10 @@ import java.io.Serializable;
|
|||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "removeClientSessionNoteByUser", query="delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"),
|
||||
@NamedQuery(name = "removeClientSessionNoteByClient", query="delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.session IN (select s from UserSessionEntity s where s.realmId = :realmId))"),
|
||||
@NamedQuery(name = "removeClientSessionNoteByRealm", query="delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId))"),
|
||||
@NamedQuery(name = "removeClientSessionNoteByExpired", query = "delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))")
|
||||
@NamedQuery(name = "removeClientSessionNoteByClient", query="delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionNoteByRealm", query="delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionNoteByExpired", query = "delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))"),
|
||||
@NamedQuery(name = "removeDetachedClientSessionNoteByExpired", query = "delete from ClientSessionNoteEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IS NULL and c.realmId = :realmId and c.timestamp < :maxTime )")
|
||||
})
|
||||
@Table(name="CLIENT_SESSION_NOTE")
|
||||
@Entity
|
||||
|
|
|
@ -17,9 +17,10 @@ import java.io.Serializable;
|
|||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "removeClientSessionRoleByUser", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"),
|
||||
@NamedQuery(name = "removeClientSessionRoleByClient", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.session IN (select s from UserSessionEntity s where s.realmId = :realmId))"),
|
||||
@NamedQuery(name = "removeClientSessionRoleByRealm", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId))"),
|
||||
@NamedQuery(name = "removeClientSessionRoleByExpired", query = "delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))")
|
||||
@NamedQuery(name = "removeClientSessionRoleByClient", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionRoleByRealm", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"),
|
||||
@NamedQuery(name = "removeClientSessionRoleByExpired", query = "delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))"),
|
||||
@NamedQuery(name = "removeDetachedClientSessionRoleByExpired", query = "delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IS NULL and c.realmId = :realmId and c.timestamp < :maxTime )")
|
||||
})
|
||||
@Table(name="CLIENT_SESSION_ROLE")
|
||||
@Entity
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
|
||||
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -31,19 +32,40 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return session.realms().getRealm(entity.getRealmId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ClientModel getClient() {
|
||||
return realm.findClientById(entity.getClientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return entity.getState();
|
||||
public UserSessionModel getUserSession() {
|
||||
if (entity.getSession() == null) return null;
|
||||
return new UserSessionAdapter(session, provider, realm, entity.getSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession() {
|
||||
return new UserSessionAdapter(session, provider, realm, entity.getSession());
|
||||
public void setUserSession(UserSessionModel userSession) {
|
||||
UserSessionAdapter adapter = (UserSessionAdapter)userSession;
|
||||
UserSessionEntity userSessionEntity = adapter.getEntity();
|
||||
entity.setSession(userSessionEntity);
|
||||
userSessionEntity.getClientSessions().add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUri(String uri) {
|
||||
entity.setRedirectUri(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoles(Set<String> roles) {
|
||||
entity.setRoles(roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -42,20 +42,12 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles) {
|
||||
UserSessionEntity userSessionEntity = getUserSessionEntity(realm, userSession.getId());
|
||||
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
||||
ClientSessionEntity entity = new ClientSessionEntity();
|
||||
entity.setId(KeycloakModelUtils.generateId());
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setClientId(client.getId());
|
||||
entity.setSession(userSessionEntity);
|
||||
entity.setRedirectUri(redirectUri);
|
||||
entity.setState(state);
|
||||
entity.setRoles(roles);
|
||||
|
||||
userSessionEntity.addClientSession(entity);
|
||||
|
||||
entity.setRealmId(realm.getId());
|
||||
clientSessions.put(entity.getId(), entity);
|
||||
return new ClientSessionAdapter(session, this, realm, entity);
|
||||
}
|
||||
|
@ -66,6 +58,16 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
return entity != null ? new ClientSessionAdapter(session, this, realm, entity) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(String id) {
|
||||
ClientSessionEntity entity = clientSessions.get(id);
|
||||
if (entity != null) {
|
||||
RealmModel realm = session.realms().getRealm(entity.getRealmId());
|
||||
return new ClientSessionAdapter(session, this, realm, entity);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
||||
String id = KeycloakModelUtils.generateId();
|
||||
|
@ -118,7 +120,9 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
|
||||
for (ClientSessionEntity s : clientSessions.values()) {
|
||||
if (s.getSession().getRealm().equals(realm.getId()) && s.getClientId().equals(client.getId())) {
|
||||
String realmId = realm.getId();
|
||||
String clientId = client.getId();
|
||||
if (s.getSession().getRealm().equals(realmId) && s.getClientId().equals(clientId)) {
|
||||
if (!userSessionEntities.contains(s.getSession())) {
|
||||
userSessionEntities.add(s.getSession());
|
||||
}
|
||||
|
@ -188,6 +192,13 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
|
||||
while (citr.hasNext()) {
|
||||
ClientSessionEntity c = citr.next();
|
||||
if (c.getSession() == null && c.getTimestamp() < Time.currentTime() - realm.getSsoSessionIdleTimeout()) {
|
||||
citr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -203,6 +214,13 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
|
||||
while (citr.hasNext()) {
|
||||
ClientSessionEntity c = citr.next();
|
||||
if (c.getSession() == null && c.getRealmId().equals(realm.getId())) {
|
||||
citr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,6 +30,10 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||
this.entity = entity;
|
||||
}
|
||||
|
||||
public UserSessionEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ public class ClientSessionEntity {
|
|||
|
||||
private String id;
|
||||
private String clientId;
|
||||
private String realmId;
|
||||
|
||||
private UserSessionEntity session;
|
||||
|
||||
private String redirectUri;
|
||||
private String state;
|
||||
private String authMethod;
|
||||
|
||||
private int timestamp;
|
||||
|
@ -41,6 +41,14 @@ public class ClientSessionEntity {
|
|||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public UserSessionEntity getSession() {
|
||||
return session;
|
||||
}
|
||||
|
@ -57,14 +65,6 @@ public class ClientSessionEntity {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
|
|||
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -21,15 +23,13 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
private MongoUserSessionProvider provider;
|
||||
private RealmModel realm;
|
||||
private MongoClientSessionEntity entity;
|
||||
private MongoUserSessionEntity userSessionEntity;
|
||||
private MongoStoreInvocationContext invContext;
|
||||
|
||||
public ClientSessionAdapter(KeycloakSession session, MongoUserSessionProvider provider, RealmModel realm, MongoClientSessionEntity entity, MongoUserSessionEntity userSessionEntity, MongoStoreInvocationContext invContext) {
|
||||
public ClientSessionAdapter(KeycloakSession session, MongoUserSessionProvider provider, RealmModel realm, MongoClientSessionEntity entity, MongoStoreInvocationContext invContext) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
this.userSessionEntity = userSessionEntity;
|
||||
this.invContext = invContext;
|
||||
}
|
||||
|
||||
|
@ -38,19 +38,40 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return session.realms().getRealm(entity.getRealmId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClient() {
|
||||
return realm.findClientById(entity.getClientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return entity.getState();
|
||||
public UserSessionModel getUserSession() {
|
||||
if (entity.getSessionId() == null) return null;
|
||||
return provider.getUserSession(realm, entity.getSessionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession() {
|
||||
return new UserSessionAdapter(session, provider, userSessionEntity, realm, invContext);
|
||||
public void setUserSession(UserSessionModel userSession) {
|
||||
MongoUserSessionEntity userSessionEntity = provider.getUserSessionEntity(realm, userSession.getId());
|
||||
entity.setSessionId(userSessionEntity.getId());
|
||||
provider.getMongoStore().pushItemToList(userSessionEntity, "clientSessions", entity.getId(), true, invContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUri(String uri) {
|
||||
entity.setRedirectUri(uri);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoles(Set<String> roles) {
|
||||
List<String> list = new LinkedList<String>();
|
||||
list.addAll(roles);
|
||||
entity.setRoles(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,7 +87,6 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
@Override
|
||||
public void setTimestamp(int timestamp) {
|
||||
entity.setTimestamp(timestamp);
|
||||
invContext.getMongoStore().updateEntity(userSessionEntity, invContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,7 +97,6 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
@Override
|
||||
public void setAction(Action action) {
|
||||
entity.setAction(action);
|
||||
invContext.getMongoStore().updateEntity(userSessionEntity, invContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -38,49 +38,37 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
|||
this.invocationContext = invocationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles) {
|
||||
MongoUserSessionEntity userSessionEntity = getUserSessionEntity(realm, userSession.getId());
|
||||
public MongoStore getMongoStore() {
|
||||
return mongoStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
|
||||
MongoClientSessionEntity entity = new MongoClientSessionEntity();
|
||||
entity.setId(KeycloakModelUtils.generateId());
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setClientId(client.getId());
|
||||
entity.setRedirectUri(redirectUri);
|
||||
entity.setState(state);
|
||||
if (roles != null) {
|
||||
entity.setRoles(new LinkedList<String>(roles));
|
||||
}
|
||||
|
||||
mongoStore.pushItemToList(userSessionEntity, "clientSessions", entity, false, invocationContext);
|
||||
|
||||
return new ClientSessionAdapter(session, this, realm, entity, userSessionEntity, invocationContext);
|
||||
entity.setRealmId(realm.getId());
|
||||
return new ClientSessionAdapter(session, this, realm, entity, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(RealmModel realm, String id) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.and("clientSessions.id").is(id).get();
|
||||
MongoClientSessionEntity entity = getClientSessionEntity(id);
|
||||
if (entity == null) return null;
|
||||
return new ClientSessionAdapter(session, this, realm, entity, invocationContext);
|
||||
}
|
||||
|
||||
List<MongoUserSessionEntity> entities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
if (entities.isEmpty()) {
|
||||
@Override
|
||||
public ClientSessionModel getClientSession(String id) {
|
||||
MongoClientSessionEntity entity = getClientSessionEntity(id);
|
||||
if (entity != null) {
|
||||
RealmModel realm = session.realms().getRealm(entity.getRealmId());
|
||||
return new ClientSessionAdapter(session, this, realm, entity, invocationContext);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
MongoUserSessionEntity userSessionEntity = entities.get(0);
|
||||
List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions();
|
||||
if (sessions == null) {
|
||||
return null;
|
||||
}
|
||||
for (MongoClientSessionEntity s : sessions) {
|
||||
if (s.getId().equals(id)) {
|
||||
return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
||||
|
@ -91,6 +79,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
|||
entity.setIpAddress(ipAddress);
|
||||
entity.setAuthMethod(authMethod);
|
||||
entity.setRememberMe(rememberMe);
|
||||
entity.setRealmId(realm.getId());
|
||||
|
||||
int currentTime = Time.currentTime();
|
||||
|
||||
|
@ -115,6 +104,10 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
|||
return mongoStore.loadEntity(MongoUserSessionEntity.class, id, invocationContext);
|
||||
}
|
||||
|
||||
MongoClientSessionEntity getClientSessionEntity(String id) {
|
||||
return mongoStore.loadEntity(MongoClientSessionEntity.class, id, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user) {
|
||||
DBObject query = new BasicDBObject("user", user.getId());
|
||||
|
@ -167,21 +160,35 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
|||
public void removeUserSessions(RealmModel realm) {
|
||||
DBObject query = new BasicDBObject("realmId", realm.getId());
|
||||
mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.get();
|
||||
|
||||
mongoStore.removeEntities(MongoClientSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredUserSessions(RealmModel realm) {
|
||||
int currentTime = Time.currentTime();
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.and("started").lessThan(currentTime - realm.getSsoSessionMaxLifespan())
|
||||
.get();
|
||||
|
||||
mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.and("lastSessionRefresh").lessThan(currentTime - realm.getSsoSessionIdleTimeout())
|
||||
.get();
|
||||
|
||||
mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
query = new QueryBuilder()
|
||||
.and("sessionId").is(null)
|
||||
.and("realmId").is(realm.getId())
|
||||
.and("timestamp").lessThan(currentTime - realm.getSsoSessionIdleTimeout())
|
||||
.get();
|
||||
|
||||
mongoStore.removeEntities(MongoClientSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -239,24 +246,9 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
|||
// TODO Not very efficient, should use Mongo $pull to remove directly
|
||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("clientSessions.clientId").is(client.getId())
|
||||
.and("clientId").is(client.getId())
|
||||
.get();
|
||||
List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
for (MongoUserSessionEntity e : userSessionEntities) {
|
||||
if (e.getClientSessions() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>();
|
||||
for (MongoClientSessionEntity c : e.getClientSessions()) {
|
||||
if (c.getClientId().equals(client.getId())) {
|
||||
remove.add(c);
|
||||
}
|
||||
}
|
||||
for (MongoClientSessionEntity c : remove) {
|
||||
mongoStore.pullItemFromList(e, "clientSessions", c, invocationContext);
|
||||
}
|
||||
}
|
||||
mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -135,8 +135,10 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
|||
return sessions;
|
||||
}
|
||||
|
||||
for (MongoClientSessionEntity e : entity.getClientSessions()) {
|
||||
sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext));
|
||||
for (String id : entity.getClientSessions()) {
|
||||
ClientSessionModel clientSession = provider.getClientSession(realm, id);
|
||||
if (clientSession == null) continue;
|
||||
sessions.add(clientSession);
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package org.keycloak.models.sessions.mongo.entities;
|
||||
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -9,13 +12,14 @@ import java.util.Map;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoClientSessionEntity {
|
||||
public class MongoClientSessionEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
|
||||
|
||||
private String id;
|
||||
private String clientId;
|
||||
private String realmId;
|
||||
private String sessionId;
|
||||
|
||||
private String redirectUri;
|
||||
private String state;
|
||||
private String authMethod;
|
||||
|
||||
private int timestamp;
|
||||
|
@ -39,6 +43,14 @@ public class MongoClientSessionEntity {
|
|||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
@ -47,14 +59,6 @@ public class MongoClientSessionEntity {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getAuthMethod() {
|
||||
return authMethod;
|
||||
}
|
||||
|
@ -94,4 +98,17 @@ public class MongoClientSessionEntity {
|
|||
public void setNotes(Map<String, String> notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.keycloak.models.sessions.mongo.entities;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -29,7 +32,7 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
|||
|
||||
private int lastSessionRefresh;
|
||||
|
||||
private List<MongoClientSessionEntity> clientSessions;
|
||||
private List<String> clientSessions = new ArrayList<String>();
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
|
@ -95,16 +98,20 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
|||
this.lastSessionRefresh = lastSessionRefresh;
|
||||
}
|
||||
|
||||
public List<MongoClientSessionEntity> getClientSessions() {
|
||||
public List<String> getClientSessions() {
|
||||
return clientSessions;
|
||||
}
|
||||
|
||||
public void setClientSessions(List<MongoClientSessionEntity> clientSessions) {
|
||||
public void setClientSessions(List<String> clientSessions) {
|
||||
this.clientSessions = clientSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("sessionId").is(getId())
|
||||
.get();
|
||||
context.getMongoStore().removeEntities(MongoClientSessionEntity.class, query, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ public class AccessCode {
|
|||
}
|
||||
|
||||
public String getState() {
|
||||
return clientSession.getState();
|
||||
throw new RuntimeException("REFACTORING, TODO REMOVE ACCESS CODE");
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.services.managers;
|
|||
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
@ -18,6 +19,7 @@ public class Auth {
|
|||
private final UserModel user;
|
||||
private final ClientModel client;
|
||||
private final UserSessionModel session;
|
||||
private ClientSessionModel clientSession;
|
||||
|
||||
public Auth(RealmModel realm, AccessToken token, UserModel user, ClientModel client, UserSessionModel session, boolean cookie) {
|
||||
this.cookie = cookie;
|
||||
|
@ -53,6 +55,14 @@ public class Auth {
|
|||
return session;
|
||||
}
|
||||
|
||||
public ClientSessionModel getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public void setClientSession(ClientSessionModel clientSession) {
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public boolean hasRealmRole(String role) {
|
||||
if (cookie) {
|
||||
return user.hasRole(realm.getRole(role));
|
||||
|
|
173
services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
Executable file
173
services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
Executable file
|
@ -0,0 +1,173 @@
|
|||
package org.keycloak.services.managers;
|
||||
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.util.Base64Url;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.Signature;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClientSessionCode {
|
||||
|
||||
private final RealmModel realm;
|
||||
private final ClientSessionModel clientSession;
|
||||
|
||||
public ClientSessionCode(RealmModel realm, ClientSessionModel clientSession) {
|
||||
this.realm = realm;
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public static ClientSessionCode parse(String code, KeycloakSession session) {
|
||||
try {
|
||||
String[] parts = code.split("\\.");
|
||||
String id = new String(Base64Url.decode(parts[1]));
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().getClientSession(id);
|
||||
if (clientSession == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String hash = createSignatureHash(clientSession.getRealm(), clientSession);
|
||||
if (!hash.equals(parts[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ClientSessionCode(clientSession.getRealm(), clientSession);
|
||||
} catch (RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
|
||||
try {
|
||||
String[] parts = code.split("\\.");
|
||||
String id = new String(Base64Url.decode(parts[1]));
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
|
||||
if (clientSession == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String hash = createSignatureHash(realm, clientSession);
|
||||
if (!hash.equals(parts[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ClientSessionCode(realm, clientSession);
|
||||
} catch (RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ClientSessionModel getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public boolean isValid(RequiredAction requiredAction) {
|
||||
return isValid(convertToAction(requiredAction));
|
||||
}
|
||||
|
||||
public boolean isValid(ClientSessionModel.Action requestedAction) {
|
||||
ClientSessionModel.Action action = clientSession.getAction();
|
||||
if (action == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int timestamp = clientSession.getTimestamp();
|
||||
|
||||
if (!action.equals(requestedAction)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int lifespan = action.equals(ClientSessionModel.Action.CODE_TO_TOKEN) ? realm.getAccessCodeLifespan() : realm.getAccessCodeLifespanUserAction();
|
||||
return timestamp + lifespan > Time.currentTime();
|
||||
}
|
||||
|
||||
public Set<RoleModel> getRequestedRoles() {
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
for (String roleId : clientSession.getRoles()) {
|
||||
RoleModel role = realm.getRoleById(roleId);
|
||||
if (role == null) {
|
||||
new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid role " + roleId);
|
||||
}
|
||||
requestedRoles.add(realm.getRoleById(roleId));
|
||||
}
|
||||
return requestedRoles;
|
||||
}
|
||||
|
||||
public void setAction(ClientSessionModel.Action action) {
|
||||
clientSession.setAction(action);
|
||||
clientSession.setTimestamp(Time.currentTime());
|
||||
}
|
||||
|
||||
public void setRequiredAction(RequiredAction requiredAction) {
|
||||
setAction(convertToAction(requiredAction));
|
||||
}
|
||||
|
||||
private ClientSessionModel.Action convertToAction(RequiredAction requiredAction) {
|
||||
switch (requiredAction) {
|
||||
case CONFIGURE_TOTP:
|
||||
return ClientSessionModel.Action.CONFIGURE_TOTP;
|
||||
case UPDATE_PASSWORD:
|
||||
return ClientSessionModel.Action.UPDATE_PASSWORD;
|
||||
case UPDATE_PROFILE:
|
||||
return ClientSessionModel.Action.UPDATE_PROFILE;
|
||||
case VERIFY_EMAIL:
|
||||
return ClientSessionModel.Action.VERIFY_EMAIL;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown required action " + requiredAction);
|
||||
}
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return generateCode(realm, clientSession);
|
||||
}
|
||||
|
||||
private static String generateCode(RealmModel realm, ClientSessionModel clientSession) {
|
||||
String hash = createSignatureHash(realm, clientSession);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(hash);
|
||||
sb.append(".");
|
||||
sb.append(Base64Url.encode(clientSession.getId().getBytes()));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String createSignatureHash(RealmModel realm, ClientSessionModel clientSession) {
|
||||
try {
|
||||
Signature signature = Signature.getInstance(RSAProvider.getJavaAlgorithm(Algorithm.RS256));
|
||||
signature.initSign(realm.getPrivateKey());
|
||||
signature.update(clientSession.getId().getBytes());
|
||||
signature.update(ByteBuffer.allocate(4).putInt(clientSession.getTimestamp()));
|
||||
if (clientSession.getAction() != null) {
|
||||
signature.update(clientSession.getAction().toString().getBytes());
|
||||
}
|
||||
byte[] sign = signature.sign();
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance("sha-1");
|
||||
digest.update(sign);
|
||||
return Base64Url.encode(digest.digest());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -53,16 +53,6 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
public AccessCode createAccessCode(String scopeParam, String state, String redirect, KeycloakSession session, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession) {
|
||||
Set<String> requestedRoles = new HashSet<String>();
|
||||
for (RoleModel r : getAccess(scopeParam, client, user)) {
|
||||
requestedRoles.add(r.getId());
|
||||
}
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession, redirect, state, requestedRoles);
|
||||
return new AccessCode(realm, clientSession);
|
||||
}
|
||||
|
||||
public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
|
||||
RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
|
||||
|
||||
|
@ -132,7 +122,21 @@ public class TokenManager {
|
|||
return token;
|
||||
}
|
||||
|
||||
public Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
|
||||
public static void attachClientSession(UserSessionModel session, ClientSessionModel clientSession) {
|
||||
UserModel user = session.getUser();
|
||||
clientSession.setUserSession(session);
|
||||
Set<String> requestedRoles = new HashSet<String>();
|
||||
// todo scope param protocol independent
|
||||
for (RoleModel r : TokenManager.getAccess(null, clientSession.getClient(), user)) {
|
||||
requestedRoles.add(r.getId());
|
||||
}
|
||||
clientSession.setRoles(requestedRoles);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
|
||||
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
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";
|
||||
}
|
|
@ -54,7 +54,9 @@ import org.keycloak.services.ForbiddenException;
|
|||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
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.OAuthRedirect;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
|
@ -191,12 +193,15 @@ public class AccountService {
|
|||
boolean associated = false;
|
||||
for (ClientSessionModel c : userSession.getClientSessions()) {
|
||||
if (c.getClient().equals(application)) {
|
||||
auth.setClientSession(c);
|
||||
associated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!associated) {
|
||||
session.sessions().createClientSession(realm, application, userSession, null, null, null);
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, application);
|
||||
clientSession.setUserSession(userSession);
|
||||
auth.setClientSession(clientSession);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -650,12 +655,13 @@ public class AccountService {
|
|||
String redirectUri = UriBuilder.fromUri(Urls.accountSocialPage(uriInfo.getBaseUri(), realm.getName())).build().toString();
|
||||
|
||||
try {
|
||||
ClientSessionModel clientSession = auth.getClientSession();
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setRedirectUri(redirectUri);
|
||||
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, UUID.randomUUID().toString());
|
||||
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
|
||||
return Flows.social(realm, uriInfo, clientConnection, provider)
|
||||
.user(user)
|
||||
.putClientAttribute(OAuth2Constants.CLIENT_ID, Constants.ACCOUNT_MANAGEMENT_APP)
|
||||
.putClientAttribute(OAuth2Constants.STATE, UUID.randomUUID().toString())
|
||||
.putClientAttribute(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.redirectToSocialProvider();
|
||||
.redirectToSocialProvider(clientSessionCode);
|
||||
} catch (SocialProviderException spe) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Messages.SOCIAL_REDIRECT_ERROR).createResponse(AccountPages.SOCIAL);
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
|
@ -44,8 +45,10 @@ import org.keycloak.models.utils.TimeBasedOTP;
|
|||
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.messages.Messages;
|
||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
|
@ -54,6 +57,7 @@ import javax.ws.rs.Consumes;
|
|||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
@ -63,6 +67,7 @@ import javax.ws.rs.core.UriBuilder;
|
|||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Providers;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
@ -102,22 +107,66 @@ public class RequiredActionsService {
|
|||
this.event = event;
|
||||
}
|
||||
|
||||
private boolean checkSsl() {
|
||||
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||
return true;
|
||||
} else {
|
||||
return !realm.getSslRequired().isRequired(clientConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class Checks {
|
||||
ClientSessionCode clientCode;
|
||||
Response response;
|
||||
|
||||
boolean check(String code, ClientSessionModel.Action requiredAction) {
|
||||
if (!checkSsl()) {
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
return false;
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
return false;
|
||||
}
|
||||
clientCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (clientCode == null) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
|
||||
return false;
|
||||
}
|
||||
if (!clientCode.isValid(requiredAction)) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Path("profile")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response updateProfile(final MultivaluedMap<String, String> formData) {
|
||||
AccessCode accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
|
||||
if (accessCode == null) {
|
||||
return unauthorized();
|
||||
public Response updateProfile(@QueryParam("code") String code,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.UPDATE_PROFILE);
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) {
|
||||
return checks.response;
|
||||
}
|
||||
ClientSessionCode accessCode = checks.clientCode;
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
UserModel user = userSession.getUser();
|
||||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initEvent(accessCode);
|
||||
initEvent(clientSession);
|
||||
|
||||
String error = Validation.validateUpdateProfileForm(formData);
|
||||
if (error != null) {
|
||||
return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error).createResponse(RequiredAction.UPDATE_PROFILE);
|
||||
return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.UPDATE_PROFILE);
|
||||
}
|
||||
|
||||
user.setFirstName(formData.getFirst("firstName"));
|
||||
|
@ -137,30 +186,38 @@ public class RequiredActionsService {
|
|||
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
||||
}
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
return redirectOauth(user, accessCode, clientSession, userSession);
|
||||
}
|
||||
|
||||
@Path("totp")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response updateTotp(final MultivaluedMap<String, String> formData) {
|
||||
AccessCode accessCode = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
|
||||
if (accessCode == null) {
|
||||
return unauthorized();
|
||||
public Response updateTotp(@QueryParam("code") String code,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.UPDATE_TOTP);
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) {
|
||||
return checks.response;
|
||||
}
|
||||
ClientSessionCode accessCode = checks.clientCode;
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
UserModel user = userSession.getUser();
|
||||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initEvent(accessCode);
|
||||
initEvent(clientSession);
|
||||
|
||||
String totp = formData.getFirst("totp");
|
||||
String totpSecret = formData.getFirst("totpSecret");
|
||||
|
||||
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
|
||||
if (Validation.isEmpty(totp)) {
|
||||
return loginForms.setError(Messages.MISSING_TOTP).createResponse(RequiredAction.CONFIGURE_TOTP);
|
||||
return loginForms.setError(Messages.MISSING_TOTP)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.CONFIGURE_TOTP);
|
||||
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
|
||||
return loginForms.setError(Messages.INVALID_TOTP).createResponse(RequiredAction.CONFIGURE_TOTP);
|
||||
return loginForms.setError(Messages.INVALID_TOTP)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.CONFIGURE_TOTP);
|
||||
}
|
||||
|
||||
UserCredentialModel credentials = new UserCredentialModel();
|
||||
|
@ -174,113 +231,163 @@ public class RequiredActionsService {
|
|||
|
||||
event.clone().event(EventType.UPDATE_TOTP).success();
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
return redirectOauth(user, accessCode, clientSession, userSession);
|
||||
}
|
||||
|
||||
@Path("password")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response updatePassword(final MultivaluedMap<String, String> formData) {
|
||||
AccessCode accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
|
||||
if (accessCode == null) {
|
||||
return unauthorized();
|
||||
public Response updatePassword(@QueryParam("code") String code,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.UPDATE_PASSWORD);
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD)) {
|
||||
return checks.response;
|
||||
}
|
||||
ClientSessionCode accessCode = checks.clientCode;
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
UserModel user = userSession.getUser();
|
||||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initEvent(accessCode);
|
||||
initEvent(clientSession);
|
||||
|
||||
String passwordNew = formData.getFirst("password-new");
|
||||
String passwordConfirm = formData.getFirst("password-confirm");
|
||||
|
||||
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
|
||||
if (Validation.isEmpty(passwordNew)) {
|
||||
return loginForms.setError(Messages.MISSING_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
return loginForms.setError(Messages.MISSING_PASSWORD)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
} else if (!passwordNew.equals(passwordConfirm)) {
|
||||
return loginForms.setError(Messages.NOTMATCH_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
return loginForms.setError(Messages.NOTMATCH_PASSWORD)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
|
||||
try {
|
||||
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
|
||||
} catch (Exception ape) {
|
||||
return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
return loginForms.setError(ape.getMessage())
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
|
||||
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
event.clone().event(EventType.UPDATE_PASSWORD).success();
|
||||
return redirectOauth(user, accessCode, clientSession, userSession);
|
||||
|
||||
// Redirect to account management to login if password reset was initiated by admin
|
||||
/* here while refactoring, ok to remove when you want
|
||||
if (accessCode.getSessionState() == null) {
|
||||
return Response.seeOther(Urls.accountPage(uriInfo.getBaseUri(), realm.getId())).build();
|
||||
} else {
|
||||
return redirectOauth(user, accessCode);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@Path("email-verification")
|
||||
@GET
|
||||
public Response emailVerification() {
|
||||
public Response emailVerification(@QueryParam("code") String code) {
|
||||
event.event(EventType.VERIFY_EMAIL);
|
||||
if (uriInfo.getQueryParameters().containsKey("key")) {
|
||||
AccessCode accessCode = AccessCode.parse(uriInfo.getQueryParameters().getFirst("key"), session, realm);
|
||||
if (accessCode == null || !accessCode.isValid(RequiredAction.VERIFY_EMAIL)) {
|
||||
return unauthorized();
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
|
||||
return checks.response;
|
||||
}
|
||||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initEvent(accessCode);
|
||||
|
||||
ClientSessionCode accessCode = checks.clientCode;
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
UserModel user = userSession.getUser();
|
||||
String key = uriInfo.getQueryParameters().getFirst("key");
|
||||
String keyNote = clientSession.getNote("key");
|
||||
if (key == null || !key.equals(keyNote)) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your email.");
|
||||
}
|
||||
initEvent(clientSession);
|
||||
user.setEmailVerified(true);
|
||||
|
||||
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
|
||||
|
||||
event.clone().event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
|
||||
event.clone().event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
return redirectOauth(user, accessCode, clientSession, userSession);
|
||||
} else {
|
||||
AccessCode accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
|
||||
if (accessCode == null) {
|
||||
return unauthorized();
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) {
|
||||
return checks.response;
|
||||
}
|
||||
ClientSessionCode accessCode = checks.clientCode;
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
String verifyCode = UUID.randomUUID().toString();
|
||||
clientSession.setNote("key", verifyCode);
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
UserModel user = userSession.getUser();
|
||||
|
||||
initEvent(accessCode);
|
||||
initEvent(clientSession);
|
||||
|
||||
return Flows.forms(session, realm, null, uriInfo).setAccessCode(accessCode.getCode()).setUser(accessCode.getUser())
|
||||
return Flows.forms(session, realm, null, uriInfo)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.setVerifyCode(verifyCode)
|
||||
.setUser(userSession.getUser())
|
||||
.createResponse(RequiredAction.VERIFY_EMAIL);
|
||||
}
|
||||
}
|
||||
|
||||
@Path("password-reset")
|
||||
@GET
|
||||
public Response passwordReset() {
|
||||
public Response passwordReset(@QueryParam("code") String code) {
|
||||
event.event(EventType.SEND_RESET_PASSWORD);
|
||||
if (uriInfo.getQueryParameters().containsKey("key")) {
|
||||
AccessCode accessCode = AccessCode.parse(uriInfo.getQueryParameters().getFirst("key"), session, realm);
|
||||
if (accessCode == null || !accessCode.isValid(RequiredAction.UPDATE_PASSWORD)) {
|
||||
return unauthorized();
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD)) {
|
||||
return checks.response;
|
||||
}
|
||||
|
||||
return Flows.forms(session, realm, null, uriInfo).setAccessCode(accessCode.getCode()).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
ClientSessionCode accessCode = checks.clientCode;
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
UserModel user = userSession.getUser();
|
||||
String key = uriInfo.getQueryParameters().getFirst("key");
|
||||
String keyNote = clientSession.getNote("key");
|
||||
if (key == null || !key.equals(keyNote)) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
|
||||
}
|
||||
return Flows.forms(session, realm, null, uriInfo)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
} else {
|
||||
return Flows.forms(session, realm, null, uriInfo).createPasswordReset();
|
||||
return Flows.forms(session, realm, null, uriInfo)
|
||||
.setAccessCode(code)
|
||||
.createPasswordReset();
|
||||
}
|
||||
}
|
||||
|
||||
@Path("password-reset")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response sendPasswordReset(final MultivaluedMap<String, String> formData) {
|
||||
public Response sendPasswordReset(@QueryParam("code") String code,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.SEND_RESET_PASSWORD);
|
||||
if (!checkSsl()) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
}
|
||||
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (accessCode == null) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
|
||||
}
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
|
||||
String username = formData.getFirst("username");
|
||||
|
||||
String scopeParam = uriInfo.getQueryParameters().getFirst(OAuth2Constants.SCOPE);
|
||||
String state = uriInfo.getQueryParameters().getFirst(OAuth2Constants.STATE);
|
||||
String redirect = uriInfo.getQueryParameters().getFirst(OAuth2Constants.REDIRECT_URI);
|
||||
String clientId = uriInfo.getQueryParameters().getFirst(OAuth2Constants.CLIENT_ID);
|
||||
|
||||
AuthenticationManager authManager = new AuthenticationManager();
|
||||
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
ClientModel client = clientSession.getClient();
|
||||
if (client == null) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,
|
||||
"Unknown login requester.");
|
||||
|
@ -290,8 +397,8 @@ public class RequiredActionsService {
|
|||
"Login requester not enabled.");
|
||||
}
|
||||
|
||||
event.event(EventType.SEND_RESET_PASSWORD).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
event.client(client.getClientId())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.USERNAME, username);
|
||||
|
@ -306,55 +413,35 @@ public class RequiredActionsService {
|
|||
} else {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
|
||||
event.session(userSession);
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
|
||||
AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, session, realm, client, user, userSession);
|
||||
accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
try {
|
||||
UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
|
||||
builder.queryParam("key", accessCode.getCode());
|
||||
builder.queryParam("code", accessCode.getCode());
|
||||
String verifyCode = UUID.randomUUID().toString();
|
||||
clientSession.setNote("key", verifyCode);
|
||||
builder.queryParam("key", verifyCode);
|
||||
|
||||
String link = builder.build(realm.getName()).toString();
|
||||
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
||||
|
||||
this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendPasswordReset(link, expiration);
|
||||
|
||||
event.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
|
||||
event.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, clientSession.getId()).success();
|
||||
} catch (EmailException e) {
|
||||
logger.error("Failed to send password reset email", e);
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError").createErrorPage();
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError")
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.createErrorPage();
|
||||
}
|
||||
}
|
||||
|
||||
return Flows.forms(session, realm, client, uriInfo).setSuccess("emailSent").createPasswordReset();
|
||||
}
|
||||
|
||||
private AccessCode getAccessCodeEntry(RequiredAction requiredAction) {
|
||||
String code = uriInfo.getQueryParameters().getFirst(OAuth2Constants.CODE);
|
||||
if (code == null) {
|
||||
logger.debug("Code query param not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
AccessCode accessCode = AccessCode.parse(code, session, realm);
|
||||
if (accessCode == null) {
|
||||
logger.debug("Access code not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!accessCode.isValid(requiredAction)) {
|
||||
logger.debugv("Invalid access code");
|
||||
return null;
|
||||
}
|
||||
|
||||
return accessCode;
|
||||
}
|
||||
|
||||
private UserModel getUser(AccessCode accessCode) {
|
||||
return session.users().getUserByUsername(accessCode.getUser().getUsername(), realm);
|
||||
}
|
||||
|
||||
private Response redirectOauth(UserModel user, AccessCode accessCode) {
|
||||
private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||
if (accessCode == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -362,37 +449,40 @@ public class RequiredActionsService {
|
|||
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)
|
||||
return Flows.forms(session, realm, null, uriInfo)
|
||||
.setAccessCode(accessCode.getCode())
|
||||
.setUser(user)
|
||||
.createResponse(requiredActions.iterator().next());
|
||||
} else {
|
||||
logger.debugv("Redirecting to: {0}", accessCode.getRedirectUri());
|
||||
logger.debugv("Redirecting to: {0}", clientSession.getRedirectUri());
|
||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
||||
|
||||
AuthenticationManager authManager = new AuthenticationManager();
|
||||
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, accessCode.getSessionState());
|
||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
|
||||
return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager).redirectError(accessCode.getClient(), "access_denied", accessCode.getState(), accessCode.getRedirectUri());
|
||||
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, accessCode.getState(), accessCode.getRedirectUri());
|
||||
return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
|
||||
.redirectAccessCode(accessCode,
|
||||
userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
|
||||
}
|
||||
}
|
||||
|
||||
private void initEvent(AccessCode accessCode) {
|
||||
event.event(EventType.LOGIN).client(accessCode.getClient())
|
||||
.user(accessCode.getUser())
|
||||
.session(accessCode.getSessionState())
|
||||
.detail(Details.CODE_ID, accessCode.getCodeId())
|
||||
.detail(Details.REDIRECT_URI, accessCode.getRedirectUri())
|
||||
private void initEvent(ClientSessionModel clientSession) {
|
||||
event.event(EventType.LOGIN).client(clientSession.getClient())
|
||||
.user(clientSession.getUserSession().getUser())
|
||||
.session(clientSession.getUserSession().getId())
|
||||
.detail(Details.CODE_ID, clientSession.getId())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.RESPONSE_TYPE, "code");
|
||||
|
||||
UserSessionModel userSession = accessCode.getSessionState() != null ? session.sessions().getUserSession(realm, accessCode.getSessionState()) : null;
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
|
||||
if (userSession != null) {
|
||||
event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
|
||||
|
@ -402,9 +492,4 @@ public class RequiredActionsService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Response unauthorized() {
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Unauthorized request").createErrorPage();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.events.EventType;
|
|||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
|
@ -41,6 +42,7 @@ import org.keycloak.models.SocialLinkModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.EventsManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
@ -106,76 +108,55 @@ public class SocialResource {
|
|||
@GET
|
||||
@Path("callback")
|
||||
public Response callback(@QueryParam("state") String encodedState) throws URISyntaxException, IOException {
|
||||
State initialRequest;
|
||||
ClientSessionCode clientCode = null;
|
||||
ClientSessionModel clientSession = null;
|
||||
try {
|
||||
initialRequest = new JWSInput(encodedState).readJsonContent(State.class);
|
||||
clientCode = ClientSessionCode.parse(encodedState, session);
|
||||
if (clientCode == null) {
|
||||
return Flows.forms(session, null, null, uriInfo).setError("Unexpected callback").createErrorPage();
|
||||
}
|
||||
clientSession = clientCode.getClientSession();
|
||||
if (!clientCode.isValid(ClientSessionModel.Action.SOCIAL_CALLBACK)) {
|
||||
return Flows.forwardToSecurityFailurePage(session, clientSession.getRealm(), uriInfo, "Invalid code, please login again through your application.");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("Invalid social callback", t);
|
||||
return Flows.forms(session, null, null, uriInfo).setError("Unexpected callback").createErrorPage();
|
||||
}
|
||||
String providerId = clientSession.getNote("social_provider");
|
||||
SocialProvider provider = SocialLoader.load(providerId);
|
||||
|
||||
SocialProvider provider = SocialLoader.load(initialRequest.getProvider());
|
||||
|
||||
String realmName = initialRequest.getRealm();
|
||||
String authMethod = "social@" + provider.getId();
|
||||
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
RealmModel realm = clientSession.getRealm();
|
||||
|
||||
EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder()
|
||||
.event(EventType.LOGIN)
|
||||
.detail(Details.RESPONSE_TYPE, initialRequest.get(OAuth2Constants.RESPONSE_TYPE))
|
||||
.event(EventType.SOCIAL_LOGIN)
|
||||
.client(clientSession.getClient())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.AUTH_METHOD, authMethod);
|
||||
|
||||
AuthenticationManager authManager = new AuthenticationManager();
|
||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
}
|
||||
|
||||
String clientId = initialRequest.get(OAuth2Constants.CLIENT_ID);
|
||||
String redirectUri = initialRequest.get(OAuth2Constants.REDIRECT_URI);
|
||||
String scope = initialRequest.get(OAuth2Constants.SCOPE);
|
||||
String state = initialRequest.get(OAuth2Constants.STATE);
|
||||
String responseType = initialRequest.get(OAuth2Constants.RESPONSE_TYPE);
|
||||
|
||||
event.client(clientId).detail(Details.REDIRECT_URI, redirectUri);
|
||||
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
}
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
|
||||
String key = realm.getSocialConfig().get(provider.getId() + ".key");
|
||||
String secret = realm.getSocialConfig().get(provider.getId() + ".secret");
|
||||
String callbackUri = Urls.socialCallback(uriInfo.getBaseUri()).toString();
|
||||
SocialProviderConfig config = new SocialProviderConfig(key, secret, callbackUri);
|
||||
|
||||
Map<String, String[]> queryParams = getQueryParams();
|
||||
Map<String, String> attributes = getAttributes();
|
||||
|
||||
AuthCallback callback = new AuthCallback(queryParams, attributes);
|
||||
AuthCallback callback = new AuthCallback(queryParams);
|
||||
|
||||
SocialUser socialUser;
|
||||
try {
|
||||
socialUser = provider.processCallback(config, callback);
|
||||
socialUser = provider.processCallback(clientSession, config, callback);
|
||||
} catch (SocialAccessDeniedException e) {
|
||||
MultivaluedMap<String, String> queryParms = new MultivaluedMapImpl<String, String>();
|
||||
queryParms.putSingle(OAuth2Constants.CLIENT_ID, clientId);
|
||||
queryParms.putSingle(OAuth2Constants.STATE, state);
|
||||
queryParms.putSingle(OAuth2Constants.SCOPE, scope);
|
||||
queryParms.putSingle(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
queryParms.putSingle(OAuth2Constants.RESPONSE_TYPE, responseType);
|
||||
|
||||
event.error(Errors.REJECTED_BY_USER);
|
||||
return Flows.forms(session, realm, client, uriInfo).setQueryParams(queryParms).setWarning("Access denied").createLogin();
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setAccessCode(clientCode.getCode()).setWarning("Access denied").createLogin();
|
||||
} catch (SocialProviderException e) {
|
||||
logger.error("Failed to process social callback", e);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process social callback");
|
||||
|
@ -188,11 +169,11 @@ public class SocialResource {
|
|||
UserModel user = session.users().getUserBySocialLink(socialLink, realm);
|
||||
|
||||
// Check if user is already authenticated (this means linking social into existing user account)
|
||||
String userId = initialRequest.getUser();
|
||||
if (userId != null) {
|
||||
UserModel authenticatedUser = session.users().getUserById(userId, realm);
|
||||
if (clientSession.getUserSession() != null) {
|
||||
|
||||
event.event(EventType.SOCIAL_LINK).user(userId);
|
||||
UserModel authenticatedUser = clientSession.getUserSession().getUser();
|
||||
|
||||
event.event(EventType.SOCIAL_LINK).user(authenticatedUser.getId());
|
||||
|
||||
if (user != null) {
|
||||
event.error(Errors.SOCIAL_ID_IN_USE);
|
||||
|
@ -209,16 +190,11 @@ public class SocialResource {
|
|||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Insufficient permissions to link social account");
|
||||
}
|
||||
|
||||
if (redirectUri == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown redirectUri");
|
||||
}
|
||||
|
||||
session.users().addSocialLink(realm, authenticatedUser, socialLink);
|
||||
logger.debugv("Social provider {0} linked with user {1}", provider.getId(), authenticatedUser.getUsername());
|
||||
|
||||
event.success();
|
||||
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||
return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
|
@ -252,31 +228,72 @@ public class SocialResource {
|
|||
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), authMethod, false);
|
||||
event.session(userSession);
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
|
||||
Response response = oauth.processAccessCode(scope, state, redirectUri, client, user, userSession, event);
|
||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, new AuthenticationManager(), tokenManager);
|
||||
Response response = oauth.processAccessCode(clientSession, userSession, event);
|
||||
if (session.getTransaction().isActive()) {
|
||||
session.getTransaction().commit();
|
||||
}
|
||||
return response;
|
||||
} catch (ModelDuplicateException e) {
|
||||
// Assume email is the duplicate as there's nothing else atm
|
||||
return returnToLogin(realm, client, initialRequest.getAttributes(), "socialEmailExists");
|
||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.setError("socialEmailExists")
|
||||
.createLogin();
|
||||
}
|
||||
}
|
||||
|
||||
private class Checks {
|
||||
ClientSessionCode clientCode;
|
||||
Response response;
|
||||
|
||||
private boolean checkSsl(RealmModel realm) {
|
||||
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||
return true;
|
||||
} else {
|
||||
return !realm.getSslRequired().isRequired(clientConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean check(EventBuilder event, RealmModel realm, String code, ClientSessionModel.Action requiredAction) {
|
||||
if (!checkSsl(realm)) {
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
return false;
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
return false;
|
||||
}
|
||||
clientCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (clientCode == null) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
|
||||
return false;
|
||||
}
|
||||
if (!clientCode.isValid(requiredAction)) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GET
|
||||
@Path("{realm}/login")
|
||||
public Response redirectToProviderAuth(@PathParam("realm") final String realmName,
|
||||
@QueryParam("provider_id") final String providerId, @QueryParam(OAuth2Constants.CLIENT_ID) final String clientId,
|
||||
@QueryParam("scope") final String scope, @QueryParam("state") final String state,
|
||||
@QueryParam("redirect_uri") String redirectUri, @QueryParam("response_type") String responseType) {
|
||||
@QueryParam("provider_id") final String providerId,
|
||||
@QueryParam("code") String code) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
|
||||
EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder()
|
||||
.event(EventType.LOGIN).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirectUri)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.event(EventType.LOGIN)
|
||||
.detail(Details.AUTH_METHOD, "social@" + providerId);
|
||||
|
||||
SocialProvider provider = SocialLoader.load(providerId);
|
||||
|
@ -285,30 +302,16 @@ public class SocialResource {
|
|||
return Flows.forms(session, realm, null, uriInfo).setError("Social provider not found").createErrorPage();
|
||||
}
|
||||
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Unknown login requester.").createErrorPage();
|
||||
Checks checks = new Checks();
|
||||
if (!checks.check(event, realm, code, ClientSessionModel.Action.AUTHENTICATE)) {
|
||||
return checks.response;
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Login requester not enabled.").createErrorPage();
|
||||
}
|
||||
redirectUri = TokenService.verifyRedirectUri(uriInfo, redirectUri, realm, client);
|
||||
if (redirectUri == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
|
||||
}
|
||||
ClientSessionCode clientSessionCode = checks.clientCode;
|
||||
|
||||
try {
|
||||
return Flows.social(realm, uriInfo, clientConnection, provider)
|
||||
.putClientAttribute(OAuth2Constants.CLIENT_ID, clientId)
|
||||
.putClientAttribute(OAuth2Constants.SCOPE, scope)
|
||||
.putClientAttribute(OAuth2Constants.STATE, state)
|
||||
.putClientAttribute(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.putClientAttribute(OAuth2Constants.RESPONSE_TYPE, responseType)
|
||||
.redirectToSocialProvider();
|
||||
.redirectToSocialProvider(clientSessionCode);
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to redirect to social auth", t);
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Failed to redirect to social auth").createErrorPage();
|
||||
|
@ -334,52 +337,4 @@ public class SocialResource {
|
|||
return queryParams;
|
||||
}
|
||||
|
||||
private Map<String, String> getAttributes() throws IOException {
|
||||
Cookie cookie = headers.getCookies().get("KEYCLOAK_SOCIAL");
|
||||
return cookie != null ? new JWSInput(cookie.getValue()).readJsonContent(HashMap.class) : null;
|
||||
}
|
||||
|
||||
public static class State {
|
||||
private String realm;
|
||||
private String provider;
|
||||
private String user;
|
||||
private Map<String, String> attributes = new HashMap<String, String>();
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public String get(String key) {
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
public void set(String key, String value) {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,13 +36,14 @@ import org.keycloak.representations.AccessTokenResponse;
|
|||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.ForbiddenException;
|
||||
import org.keycloak.services.managers.AccessCode;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
|
||||
import org.keycloak.representations.PasswordToken;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
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.OAuthFlows;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
|
@ -462,26 +463,46 @@ public class TokenService {
|
|||
/**
|
||||
* URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY!
|
||||
*
|
||||
* @param clientId
|
||||
* @param scopeParam
|
||||
* @param state
|
||||
* @param redirect
|
||||
* @param code
|
||||
* @param formData
|
||||
* @return
|
||||
*/
|
||||
@Path("auth/request/login")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response processLogin(@QueryParam("client_id") final String clientId, @QueryParam("scope") final String scopeParam,
|
||||
@QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
|
||||
public Response processLogin(@QueryParam("code") String code,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.LOGIN);
|
||||
if (!checkSsl()) {
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
}
|
||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (clientCode == null) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
|
||||
}
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
|
||||
clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
event.client(clientSession.getClient()).error(Errors.INVALID_USER_CREDENTIALS);
|
||||
return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createLogin();
|
||||
}
|
||||
|
||||
String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
|
||||
String rememberMe = formData.getFirst("rememberMe");
|
||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
||||
|
||||
event.event(EventType.LOGIN).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
event.client(clientSession.getClient().getClientId())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.USERNAME, username);
|
||||
|
@ -492,15 +513,7 @@ public class TokenService {
|
|||
|
||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
||||
|
||||
if (!checkSsl()) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
ClientModel client = clientSession.getClient();
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
|
@ -510,15 +523,9 @@ public class TokenService {
|
|||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
|
||||
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||
if (redirect == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
if (formData.containsKey("cancel")) {
|
||||
event.error(Errors.REJECTED_BY_USER);
|
||||
return oauth.redirectError(client, "access_denied", state, redirect);
|
||||
return oauth.redirectError(client, "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
|
||||
}
|
||||
|
||||
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
|
||||
|
@ -538,28 +545,45 @@ public class TokenService {
|
|||
case SUCCESS:
|
||||
case ACTIONS_REQUIRED:
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
event.session(userSession);
|
||||
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, userSession, event);
|
||||
return oauth.processAccessCode(clientSession, userSession, event);
|
||||
case ACCOUNT_TEMPORARILY_DISABLED:
|
||||
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).setFormData(formData).createLogin();
|
||||
return Flows.forms(this.session, realm, client, uriInfo)
|
||||
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createLogin();
|
||||
case ACCOUNT_DISABLED:
|
||||
event.error(Errors.USER_DISABLED);
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
|
||||
return Flows.forms(this.session, realm, client, uriInfo)
|
||||
.setError(Messages.ACCOUNT_DISABLED)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.setFormData(formData).createLogin();
|
||||
case MISSING_TOTP:
|
||||
formData.remove(CredentialRepresentation.PASSWORD);
|
||||
|
||||
String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
|
||||
formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
|
||||
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setFormData(formData).createLoginTotp();
|
||||
return Flows.forms(this.session, realm, client, uriInfo)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createLoginTotp();
|
||||
case INVALID_USER:
|
||||
event.error(Errors.USER_NOT_FOUND);
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createLogin();
|
||||
default:
|
||||
event.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
|
||||
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createLogin();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -575,25 +599,44 @@ public class TokenService {
|
|||
/**
|
||||
* Registration
|
||||
*
|
||||
* @param clientId
|
||||
* @param scopeParam
|
||||
* @param state
|
||||
* @param redirect
|
||||
* @param code
|
||||
* @param formData
|
||||
* @return
|
||||
*/
|
||||
@Path("registrations")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response processRegister(@QueryParam("client_id") final String clientId,
|
||||
@QueryParam("scope") final String scopeParam, @QueryParam("state") final String state,
|
||||
@QueryParam("redirect_uri") String redirect, final MultivaluedMap<String, String> formData) {
|
||||
public Response processRegister(@QueryParam("code") String code,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.REGISTER);
|
||||
if (!checkSsl()) {
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
|
||||
}
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
event.error(Errors.REGISTRATION_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed");
|
||||
}
|
||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (clientCode == null) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
|
||||
}
|
||||
if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
||||
}
|
||||
|
||||
String username = formData.getFirst("username");
|
||||
String email = formData.getFirst("email");
|
||||
|
||||
event.event(EventType.REGISTER).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
event.client(clientSession.getClient())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.USERNAME, username)
|
||||
.detail(Details.EMAIL, email)
|
||||
|
@ -605,7 +648,7 @@ public class TokenService {
|
|||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
ClientModel client = clientSession.getClient();
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
|
@ -616,16 +659,6 @@ public class TokenService {
|
|||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
|
||||
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||
if (redirect == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
event.error(Errors.REGISTRATION_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed");
|
||||
}
|
||||
|
||||
List<String> requiredCredentialTypes = new LinkedList<String>();
|
||||
for (RequiredCredentialModel m : realm.getRequiredCredentials()) {
|
||||
|
@ -640,19 +673,31 @@ public class TokenService {
|
|||
|
||||
if (error != null) {
|
||||
event.error(Errors.INVALID_REGISTRATION);
|
||||
return Flows.forms(session, realm, client, uriInfo).setError(error).setFormData(formData).createRegistration();
|
||||
return Flows.forms(session, realm, client, uriInfo)
|
||||
.setError(error)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createRegistration();
|
||||
}
|
||||
|
||||
// Validate that user with this username doesn't exist in realm or any federation provider
|
||||
if (session.users().getUserByUsername(username, realm) != null) {
|
||||
event.error(Errors.USERNAME_IN_USE);
|
||||
return Flows.forms(session, realm, client, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration();
|
||||
return Flows.forms(session, realm, client, uriInfo)
|
||||
.setError(Messages.USERNAME_EXISTS)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createRegistration();
|
||||
}
|
||||
|
||||
// Validate that user with this email doesn't exist in realm or any federation provider
|
||||
if (session.users().getUserByEmail(email, realm) != null) {
|
||||
event.error(Errors.EMAIL_IN_USE);
|
||||
return Flows.forms(session, realm, client, uriInfo).setError(Messages.EMAIL_EXISTS).setFormData(formData).createRegistration();
|
||||
return Flows.forms(session, realm, client, uriInfo)
|
||||
.setError(Messages.EMAIL_EXISTS)
|
||||
.setFormData(formData)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createRegistration();
|
||||
}
|
||||
|
||||
UserModel user = session.users().addUser(realm, username);
|
||||
|
@ -680,14 +725,17 @@ public class TokenService {
|
|||
// User already registered, but force him to update password
|
||||
if (!passwordUpdateSuccessful) {
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
return Flows.forms(session, realm, client, uriInfo).setError(passwordUpdateError).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
return Flows.forms(session, realm, client, uriInfo)
|
||||
.setError(passwordUpdateError)
|
||||
.setAccessCode(clientCode.getCode())
|
||||
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
event.user(user).success();
|
||||
event.reset();
|
||||
|
||||
return processLogin(clientId, scopeParam, state, redirect, formData);
|
||||
return processLogin(code, formData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -737,8 +785,7 @@ public class TokenService {
|
|||
event.error(Errors.INVALID_CODE);
|
||||
throw new BadRequestException("Code not specified", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
||||
}
|
||||
|
||||
AccessCode accessCode = AccessCode.parse(code, session, realm);
|
||||
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (accessCode == null) {
|
||||
String[] parts = code.split("\\.");
|
||||
if (parts.length == 2) {
|
||||
|
@ -754,7 +801,8 @@ public class TokenService {
|
|||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
event.detail(Details.CODE_ID, accessCode.getCodeId());
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
event.detail(Details.CODE_ID, clientSession.getId());
|
||||
if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
|
@ -765,13 +813,13 @@ public class TokenService {
|
|||
}
|
||||
|
||||
accessCode.setAction(null);
|
||||
|
||||
event.user(accessCode.getUser());
|
||||
event.session(accessCode.getSessionState());
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
event.user(userSession.getUser());
|
||||
event.session(userSession.getId());
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, formData, event);
|
||||
|
||||
if (!client.getClientId().equals(accessCode.getClient().getClientId())) {
|
||||
if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Auth error");
|
||||
|
@ -780,7 +828,7 @@ public class TokenService {
|
|||
.build();
|
||||
}
|
||||
|
||||
UserModel user = session.users().getUserById(accessCode.getUser().getId(), realm);
|
||||
UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
|
||||
if (user == null) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
|
@ -799,7 +847,6 @@ public class TokenService {
|
|||
.build();
|
||||
}
|
||||
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, accessCode.getSessionState());
|
||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
|
@ -893,37 +940,62 @@ public class TokenService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Login page. Must be redirected to from the application or oauth client.
|
||||
* checks input and initializes variables based on a front page action like the login page or registration page
|
||||
*
|
||||
* @See <a href="http://tools.ietf.org/html/rfc6749#section-4.1">http://tools.ietf.org/html/rfc6749#section-4.1</a>
|
||||
*
|
||||
*
|
||||
* @param responseType
|
||||
* @param redirect
|
||||
* @param clientId
|
||||
* @param scopeParam
|
||||
* @param state
|
||||
* @param prompt
|
||||
* @return
|
||||
*/
|
||||
@Path("login")
|
||||
@GET
|
||||
public Response loginPage(final @QueryParam("response_type") String responseType,
|
||||
@QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
|
||||
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt,
|
||||
final @QueryParam("login_hint") String loginHint) {
|
||||
event.event(EventType.LOGIN).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
||||
private class FrontPageInitializer {
|
||||
protected String code;
|
||||
protected String clientId;
|
||||
protected String redirect;
|
||||
protected String state;
|
||||
protected String scopeParam;
|
||||
protected String responseType;
|
||||
protected String loginHint;
|
||||
protected String prompt;
|
||||
protected ClientSessionModel clientSession;
|
||||
|
||||
public Response processInput() {
|
||||
if (code != null) {
|
||||
event.detail(Details.CODE_ID, code);
|
||||
} else {
|
||||
event.client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
|
||||
}
|
||||
if (!checkSsl()) {
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||
}
|
||||
|
||||
clientSession = null;
|
||||
if (code != null) {
|
||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (clientCode == null) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
|
||||
}
|
||||
if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
|
||||
}
|
||||
clientSession = clientCode.getClientSession();
|
||||
if (!clientSession.getAuthMethod().equals(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL)) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid protocol, please login again through your application.");
|
||||
}
|
||||
state = clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM);
|
||||
scopeParam = clientSession.getNote(OpenIdConnectProtocol.SCOPE_PARAM);
|
||||
responseType = clientSession.getNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM);
|
||||
loginHint = clientSession.getNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM);
|
||||
prompt = clientSession.getNote(OpenIdConnectProtocol.PROMPT_PARAM);
|
||||
} else {
|
||||
if (state == null) {
|
||||
event.error(Errors.STATE_PARAM_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid state param.");
|
||||
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
|
@ -934,7 +1006,7 @@ public class TokenService {
|
|||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
|
||||
if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
|
||||
}
|
||||
|
@ -947,21 +1019,85 @@ public class TokenService {
|
|||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
}
|
||||
clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
|
||||
if (scopeParam != null) clientSession.setNote(OpenIdConnectProtocol.SCOPE_PARAM, scopeParam);
|
||||
if (responseType != null) clientSession.setNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM, responseType);
|
||||
if (loginHint != null) clientSession.setNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM, loginHint);
|
||||
if (prompt != null) clientSession.setNote(OpenIdConnectProtocol.PROMPT_PARAM, prompt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login page. Must be redirected to from the application or oauth client.
|
||||
*
|
||||
* @See <a href="http://tools.ietf.org/html/rfc6749#section-4.1">http://tools.ietf.org/html/rfc6749#section-4.1</a>
|
||||
*
|
||||
*
|
||||
* @param code
|
||||
* @param responseType
|
||||
* @param redirect
|
||||
* @param clientId
|
||||
* @param scopeParam
|
||||
* @param state
|
||||
* @param prompt
|
||||
* @return
|
||||
*/
|
||||
@Path("login")
|
||||
@GET
|
||||
public Response loginPage(@QueryParam("code") String code,
|
||||
@QueryParam(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM) String responseType,
|
||||
@QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
|
||||
@QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
|
||||
@QueryParam(OpenIdConnectProtocol.SCOPE_PARAM) String scopeParam,
|
||||
@QueryParam(OpenIdConnectProtocol.STATE_PARAM) String state,
|
||||
@QueryParam(OpenIdConnectProtocol.PROMPT_PARAM) String prompt,
|
||||
@QueryParam(OpenIdConnectProtocol.LOGIN_HINT_PARAM) String loginHint) {
|
||||
event.event(EventType.LOGIN);
|
||||
FrontPageInitializer pageInitializer = new FrontPageInitializer();
|
||||
pageInitializer.code = code;
|
||||
pageInitializer.responseType = responseType;
|
||||
pageInitializer.redirect = redirect;
|
||||
pageInitializer.clientId = clientId;
|
||||
pageInitializer.scopeParam = scopeParam;
|
||||
pageInitializer.state = state;
|
||||
pageInitializer.prompt = prompt;
|
||||
pageInitializer.loginHint = loginHint;
|
||||
Response response = pageInitializer.processInput();
|
||||
if (response != null) return response;
|
||||
ClientSessionModel clientSession = pageInitializer.clientSession;
|
||||
code = pageInitializer.code;
|
||||
responseType = pageInitializer.responseType;
|
||||
redirect = pageInitializer.redirect;
|
||||
clientId = pageInitializer.clientId ;
|
||||
scopeParam = pageInitializer.scopeParam;
|
||||
state = pageInitializer.state;
|
||||
prompt = pageInitializer.prompt;
|
||||
loginHint = pageInitializer.loginHint;
|
||||
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
||||
|
||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers);
|
||||
if (authResult != null) {
|
||||
UserModel user = authResult.getUser();
|
||||
UserSessionModel session = authResult.getSession();
|
||||
|
||||
TokenManager.attachClientSession(session, clientSession);
|
||||
event.user(user).session(session).detail(Details.AUTH_METHOD, "sso");
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, session, event);
|
||||
return oauth.processAccessCode(clientSession, session, event);
|
||||
}
|
||||
|
||||
if (prompt != null && prompt.equals("none")) {
|
||||
return oauth.redirectError(client, "access_denied", state, redirect);
|
||||
return oauth.redirectError(clientSession.getClient(), "access_denied", state, redirect);
|
||||
}
|
||||
|
||||
LoginFormsProvider forms = Flows.forms(session, realm, client, uriInfo);
|
||||
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
.setAccessCode(new ClientSessionCode(realm, clientSession).getCode());
|
||||
|
||||
String rememberMeUsername = null;
|
||||
if (realm.isRememberMe()) {
|
||||
|
@ -999,46 +1135,35 @@ public class TokenService {
|
|||
*/
|
||||
@Path("registrations")
|
||||
@GET
|
||||
public Response registerPage(final @QueryParam("response_type") String responseType,
|
||||
@QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
|
||||
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
|
||||
event.event(EventType.REGISTER).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
|
||||
|
||||
if (!checkSsl()) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
|
||||
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||
if (redirect == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
public Response registerPage(@QueryParam("code") String code,
|
||||
@QueryParam("response_type") String responseType,
|
||||
@QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
|
||||
@QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
|
||||
@QueryParam("scope") String scopeParam,
|
||||
@QueryParam("state") String state) {
|
||||
event.event(EventType.REGISTER);
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
event.error(Errors.REGISTRATION_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed");
|
||||
}
|
||||
|
||||
FrontPageInitializer pageInitializer = new FrontPageInitializer();
|
||||
pageInitializer.code = code;
|
||||
pageInitializer.responseType = responseType;
|
||||
pageInitializer.redirect = redirect;
|
||||
pageInitializer.clientId = clientId;
|
||||
pageInitializer.scopeParam = scopeParam;
|
||||
pageInitializer.state = state;
|
||||
Response response = pageInitializer.processInput();
|
||||
if (response != null) return response;
|
||||
ClientSessionModel clientSession = pageInitializer.clientSession;
|
||||
|
||||
|
||||
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
|
||||
|
||||
return Flows.forms(session, realm, client, uriInfo).createRegistration();
|
||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
.setAccessCode(new ClientSessionCode(realm, clientSession).getCode())
|
||||
.createRegistration();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1050,7 +1175,7 @@ public class TokenService {
|
|||
@Path("logout")
|
||||
@GET
|
||||
@NoCache
|
||||
public Response logout(final @QueryParam("redirect_uri") String redirectUri) {
|
||||
public Response logout(final @QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirectUri) {
|
||||
event.event(EventType.LOGOUT);
|
||||
if (redirectUri != null) {
|
||||
event.detail(Details.REDIRECT_URI, redirectUri);
|
||||
|
@ -1150,22 +1275,22 @@ public class TokenService {
|
|||
|
||||
String code = formData.getFirst(OAuth2Constants.CODE);
|
||||
|
||||
AccessCode accessCode = AccessCode.parse(code, session, realm);
|
||||
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code.");
|
||||
}
|
||||
event.detail(Details.CODE_ID, accessCode.getCodeId());
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
event.detail(Details.CODE_ID, clientSession.getId());
|
||||
|
||||
String redirect = accessCode.getRedirectUri();
|
||||
String state = accessCode.getState();
|
||||
String redirect = clientSession.getRedirectUri();
|
||||
|
||||
event.client(accessCode.getClient())
|
||||
.user(accessCode.getUser())
|
||||
event.client(clientSession.getClient())
|
||||
.user(clientSession.getUserSession().getUser())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REDIRECT_URI, redirect);
|
||||
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, accessCode.getSessionState());
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
if (userSession != null) {
|
||||
event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
|
||||
event.detail(Details.USERNAME, userSession.getLoginUsername());
|
||||
|
@ -1183,13 +1308,13 @@ public class TokenService {
|
|||
|
||||
if (formData.containsKey("cancel")) {
|
||||
event.error(Errors.REJECTED_BY_USER);
|
||||
return redirectAccessDenied(redirect, state);
|
||||
return redirectAccessDenied(redirect, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM));
|
||||
}
|
||||
|
||||
event.success();
|
||||
|
||||
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
|
||||
return oauth.redirectAccessCode(accessCode, userSession, state, redirect);
|
||||
return oauth.redirectAccessCode(accessCode, userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), redirect);
|
||||
}
|
||||
|
||||
@Path("oauth/oob")
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.keycloak.email.EmailException;
|
|||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
|
@ -31,10 +32,12 @@ import org.keycloak.representations.idm.SocialLinkRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.services.managers.AccessCode;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
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.Urls;
|
||||
|
||||
|
@ -58,6 +61,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
@ -878,8 +882,6 @@ public class UsersResource {
|
|||
|
||||
String redirect = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
|
||||
String clientId = Constants.ACCOUNT_MANAGEMENT_APP;
|
||||
String state = null;
|
||||
String scope = null;
|
||||
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null || !client.isEnabled()) {
|
||||
|
@ -888,13 +890,20 @@ public class UsersResource {
|
|||
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
|
||||
//audit.session(userSession);
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setUserSession(userSession);
|
||||
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
|
||||
|
||||
AccessCode accessCode = tokenManager.createAccessCode(null, state, redirect, session, realm, client, user, userSession);
|
||||
accessCode.setRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
try {
|
||||
UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
|
||||
builder.queryParam("key", accessCode.getCode());
|
||||
builder.queryParam("code", accessCode.getCode());
|
||||
String key = UUID.randomUUID().toString();
|
||||
clientSession.setNote("key", key);
|
||||
builder.queryParam("key", key);
|
||||
|
||||
String link = builder.build(realm.getName()).toString();
|
||||
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
|
||||
|
|
|
@ -29,6 +29,7 @@ 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;
|
||||
|
@ -42,16 +43,20 @@ 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>
|
||||
|
@ -85,7 +90,7 @@ public class OAuthFlows {
|
|||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
public Response redirectAccessCode(AccessCode accessCode, UserSessionModel userSession, String state, String redirect) {
|
||||
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);
|
||||
|
@ -110,8 +115,8 @@ public class OAuthFlows {
|
|||
}
|
||||
|
||||
// refresh the cookies!
|
||||
authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection);
|
||||
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, accessCode.getUser().getUsername(), uriInfo, clientConnection);
|
||||
authManager.createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
|
||||
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
||||
return location.build();
|
||||
}
|
||||
|
||||
|
@ -123,6 +128,72 @@ public class OAuthFlows {
|
|||
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);
|
||||
|
@ -178,6 +249,7 @@ public class OAuthFlows {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public Response forwardToSecurityFailure(String message) {
|
||||
|
|
|
@ -2,8 +2,10 @@ package org.keycloak.services.resources.flows;
|
|||
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.resources.SocialResource;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.social.AuthRequest;
|
||||
|
@ -23,30 +25,17 @@ public class SocialRedirectFlows {
|
|||
private final UriInfo uriInfo;
|
||||
private ClientConnection clientConnection;
|
||||
private final SocialProvider socialProvider;
|
||||
private final SocialResource.State state;
|
||||
|
||||
SocialRedirectFlows(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, SocialProvider provider) {
|
||||
this.realm = realm;
|
||||
this.uriInfo = uriInfo;
|
||||
this.clientConnection = clientConnection;
|
||||
this.socialProvider = provider;
|
||||
|
||||
state = new SocialResource.State();
|
||||
state.setRealm(realm.getName());
|
||||
state.setProvider(provider.getId());
|
||||
}
|
||||
|
||||
public SocialRedirectFlows putClientAttribute(String key, String value) {
|
||||
state.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SocialRedirectFlows user(UserModel user) {
|
||||
state.setUser(user.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Response redirectToSocialProvider() throws SocialProviderException {
|
||||
public Response redirectToSocialProvider(ClientSessionCode code) throws SocialProviderException {
|
||||
code.setAction(ClientSessionModel.Action.SOCIAL_CALLBACK);
|
||||
code.getClientSession().setNote("social_provider", socialProvider.getId());
|
||||
String socialProviderId = socialProvider.getId();
|
||||
|
||||
String key = realm.getSocialConfig().get(socialProviderId + ".key");
|
||||
|
@ -54,20 +43,8 @@ public class SocialRedirectFlows {
|
|||
String callbackUri = Urls.socialCallback(uriInfo.getBaseUri()).toString();
|
||||
SocialProviderConfig config = new SocialProviderConfig(key, secret, callbackUri);
|
||||
|
||||
String encodedState = new JWSBuilder().jsonContent(state).rsa256(realm.getPrivateKey());
|
||||
|
||||
AuthRequest authRequest = socialProvider.getAuthUrl(config, encodedState);
|
||||
|
||||
if (authRequest.getAttributes() != null) {
|
||||
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getRawPath().toString();
|
||||
|
||||
String encoded = new JWSBuilder()
|
||||
.jsonContent(authRequest.getAttributes())
|
||||
.rsa256(realm.getPrivateKey());
|
||||
|
||||
CookieHelper.addCookie("KEYCLOAK_SOCIAL", encoded, cookiePath, null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
|
||||
}
|
||||
|
||||
AuthRequest authRequest = socialProvider.getAuthUrl(code.getClientSession(), config, code.getCode());
|
||||
return Response.status(302).location(authRequest.getAuthUri()).build();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.social;
|
|||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.social.utils.SimpleHttp;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -43,7 +44,7 @@ public abstract class AbstractOAuth2Provider implements SocialProvider {
|
|||
protected abstract SocialUser getProfile(String accessToken) throws SocialProviderException;
|
||||
|
||||
@Override
|
||||
public AuthRequest getAuthUrl(SocialProviderConfig config, String state) throws SocialProviderException {
|
||||
public AuthRequest getAuthUrl(ClientSessionModel clientSession, SocialProviderConfig config, String state) throws SocialProviderException {
|
||||
return AuthRequest.create(getAuthUrl())
|
||||
.setQueryParam(CLIENT_ID, config.getKey())
|
||||
.setQueryParam(RESPONSE_TYPE, CODE)
|
||||
|
@ -54,7 +55,7 @@ public abstract class AbstractOAuth2Provider implements SocialProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||
public SocialUser processCallback(ClientSessionModel clientSession, SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||
String error = callback.getQueryParam(OAuth2Constants.ERROR);
|
||||
if (error != null) {
|
||||
if (error.equals("access_denied")) {
|
||||
|
|
7
social/core/src/main/java/org/keycloak/social/AuthCallback.java
Normal file → Executable file
7
social/core/src/main/java/org/keycloak/social/AuthCallback.java
Normal file → Executable file
|
@ -29,11 +29,9 @@ import java.util.Map;
|
|||
public class AuthCallback {
|
||||
|
||||
private Map<String, String[]> queryParams;
|
||||
private Map<String, String> attributes;
|
||||
|
||||
public AuthCallback(Map<String, String[]> queryParams, Map<String, String> attributes) {
|
||||
public AuthCallback(Map<String, String[]> queryParams) {
|
||||
this.queryParams = queryParams;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getQueryParam(String name) {
|
||||
|
@ -44,8 +42,5 @@ public class AuthCallback {
|
|||
return null;
|
||||
}
|
||||
|
||||
public String getAttribute(String name) {
|
||||
return attributes != null ? attributes.get(name) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
21
social/core/src/main/java/org/keycloak/social/AuthRequest.java
Normal file → Executable file
21
social/core/src/main/java/org/keycloak/social/AuthRequest.java
Normal file → Executable file
|
@ -35,8 +35,6 @@ public class AuthRequest {
|
|||
|
||||
private URI authUri;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
|
||||
public static AuthRequestBuilder create(String url) {
|
||||
AuthRequestBuilder req = new AuthRequestBuilder();
|
||||
|
||||
|
@ -46,27 +44,20 @@ public class AuthRequest {
|
|||
return req;
|
||||
}
|
||||
|
||||
private AuthRequest(URI authUri, Map<String, String> attributes) {
|
||||
private AuthRequest(URI authUri) {
|
||||
this.authUri = authUri;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public URI getAuthUri() {
|
||||
return authUri;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public static class AuthRequestBuilder {
|
||||
|
||||
private StringBuilder b;
|
||||
|
||||
private char sep;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
|
||||
private String id;
|
||||
|
||||
private AuthRequestBuilder() {
|
||||
|
@ -90,17 +81,9 @@ public class AuthRequest {
|
|||
}
|
||||
}
|
||||
|
||||
public AuthRequestBuilder setAttribute(String name, String value) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<String, String>();
|
||||
}
|
||||
attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthRequest build() {
|
||||
try {
|
||||
return new AuthRequest(new URI(b.toString()), attributes);
|
||||
return new AuthRequest(new URI(b.toString()));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
|
6
social/core/src/main/java/org/keycloak/social/SocialProvider.java
Normal file → Executable file
6
social/core/src/main/java/org/keycloak/social/SocialProvider.java
Normal file → Executable file
|
@ -22,6 +22,8 @@
|
|||
package org.keycloak.social;
|
||||
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -29,10 +31,10 @@ public interface SocialProvider {
|
|||
|
||||
String getId();
|
||||
|
||||
AuthRequest getAuthUrl(SocialProviderConfig config, String state) throws SocialProviderException;
|
||||
AuthRequest getAuthUrl(ClientSessionModel clientSession, SocialProviderConfig config, String state) throws SocialProviderException;
|
||||
|
||||
String getName();
|
||||
|
||||
SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException;
|
||||
SocialUser processCallback(ClientSessionModel clientSession, SocialProviderConfig config, AuthCallback callback) throws SocialProviderException;
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-core</artifactId>
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-core</artifactId>
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-core</artifactId>
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-core</artifactId>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
*/
|
||||
package org.keycloak.social.twitter;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.social.AuthCallback;
|
||||
import org.keycloak.social.AuthRequest;
|
||||
import org.keycloak.social.SocialAccessDeniedException;
|
||||
|
@ -45,7 +46,7 @@ public class TwitterProvider implements SocialProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuthRequest getAuthUrl(SocialProviderConfig config, String state) throws SocialProviderException {
|
||||
public AuthRequest getAuthUrl(ClientSessionModel clientSession, SocialProviderConfig config, String state) throws SocialProviderException {
|
||||
try {
|
||||
Twitter twitter = new TwitterFactory().getInstance();
|
||||
twitter.setOAuthConsumer(config.getKey(), config.getSecret());
|
||||
|
@ -53,8 +54,9 @@ public class TwitterProvider implements SocialProvider {
|
|||
URI uri = new URI(config.getCallbackUrl() + "?state=" + state);
|
||||
|
||||
RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
|
||||
clientSession.setNote("twitter_token", requestToken.getToken());
|
||||
clientSession.setNote("twitter_tokenSecret", requestToken.getTokenSecret());
|
||||
return AuthRequest.create(requestToken.getAuthenticationURL())
|
||||
.setAttribute("token", requestToken.getToken()).setAttribute("tokenSecret", requestToken.getTokenSecret())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
throw new SocialProviderException(e);
|
||||
|
@ -67,7 +69,7 @@ public class TwitterProvider implements SocialProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||
public SocialUser processCallback(ClientSessionModel clientSession, SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||
if (callback.getQueryParam("denied") != null) {
|
||||
throw new SocialAccessDeniedException();
|
||||
}
|
||||
|
@ -78,8 +80,10 @@ public class TwitterProvider implements SocialProvider {
|
|||
|
||||
String token = callback.getQueryParam("oauth_token");
|
||||
String verifier = callback.getQueryParam("oauth_verifier");
|
||||
String twitterToken = clientSession.getNote("twitter_token");
|
||||
String twitterSecret = clientSession.getNote("twitter_tokenSecret");
|
||||
|
||||
RequestToken requestToken = new RequestToken((String)callback.getAttribute("token"), (String)callback.getAttribute("tokenSecret"));
|
||||
RequestToken requestToken = new RequestToken(twitterToken, twitterSecret);
|
||||
|
||||
twitter.getOAuthAccessToken(requestToken, verifier);
|
||||
twitter4j.User twitterUser = twitter.verifyCredentials();
|
||||
|
|
|
@ -124,6 +124,15 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
|||
.session(isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectSocialLogin() {
|
||||
return expect(EventType.SOCIAL_LOGIN)
|
||||
.detail(Details.CODE_ID, isCodeId())
|
||||
.detail(Details.USERNAME, DEFAULT_USERNAME)
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI)
|
||||
.session(isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
|
||||
return expect(EventType.CODE_TO_TOKEN)
|
||||
.detail(Details.CODE_ID, codeId)
|
||||
|
@ -277,6 +286,11 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
|||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent clearDetails() {
|
||||
if (details != null) details.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent error(String error) {
|
||||
expected.setError(error);
|
||||
return this;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.testsuite;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.social.AuthCallback;
|
||||
import org.keycloak.social.AuthRequest;
|
||||
import org.keycloak.social.SocialAccessDeniedException;
|
||||
|
@ -19,7 +20,7 @@ public class DummySocial implements SocialProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuthRequest getAuthUrl(SocialProviderConfig config, String state) throws SocialProviderException {
|
||||
public AuthRequest getAuthUrl(ClientSessionModel clientSession, SocialProviderConfig config, String state) throws SocialProviderException {
|
||||
return AuthRequest.create(AUTH_PATH)
|
||||
.setQueryParam(OAuth2Constants.RESPONSE_TYPE, "token")
|
||||
.setQueryParam(OAuth2Constants.REDIRECT_URI, config.getCallbackUrl())
|
||||
|
@ -32,7 +33,7 @@ public class DummySocial implements SocialProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SocialUser processCallback(SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||
public SocialUser processCallback(ClientSessionModel clientSession, SocialProviderConfig config, AuthCallback callback) throws SocialProviderException {
|
||||
String error = callback.getQueryParam(OAuth2Constants.ERROR);
|
||||
if (error != null) {
|
||||
throw new SocialAccessDeniedException();
|
||||
|
|
|
@ -75,7 +75,7 @@ public class OAuthClient {
|
|||
|
||||
private String redirectUri = "http://localhost:8081/app/auth";
|
||||
|
||||
private String state;
|
||||
private String state = "mystate";
|
||||
|
||||
private PublicKey realmPublicKey;
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@ public class AccountTest {
|
|||
|
||||
@Before
|
||||
public void before() {
|
||||
oauth.state("mystate"); // keycloak enforces that a state param has been sent by client
|
||||
userId = keycloakRule.getUser("test", "test-user@localhost").getId();
|
||||
}
|
||||
|
||||
|
@ -159,6 +160,16 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void returnToAppFromQueryParam() {
|
||||
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
|
||||
|
@ -446,6 +457,7 @@ public class AccountTest {
|
|||
// Create second session
|
||||
WebDriver driver2 = WebRule.createWebDriver();
|
||||
OAuthClient oauth2 = new OAuthClient(driver2);
|
||||
oauth2.state("mystate");
|
||||
oauth2.doLogin("view-sessions", "password");
|
||||
|
||||
Event login2Event = events.expectLogin().user(userId).detail(Details.USERNAME, "view-sessions").assertEvent();
|
||||
|
|
|
@ -88,6 +88,7 @@ public class RequiredActionEmailVerificationTest {
|
|||
|
||||
@Before
|
||||
public void before() {
|
||||
oauth.state("mystate"); // have to set this as keycloak validates that state is sent
|
||||
keycloakRule.configure(new KeycloakSetup() {
|
||||
|
||||
@Override
|
||||
|
@ -115,7 +116,8 @@ public class RequiredActionEmailVerificationTest {
|
|||
String body = (String) message.getContent();
|
||||
String verificationUrl = MailUtil.getLink(body);
|
||||
|
||||
Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost").assertEvent();
|
||||
AssertEvents.ExpectedEvent emailEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost");
|
||||
Event sendEvent = emailEvent.assertEvent();
|
||||
String sessionId = sendEvent.getSessionId();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
package org.keycloak.testsuite.actions;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
@ -86,6 +87,12 @@ public class RequiredActionResetPasswordTest {
|
|||
@WebResource
|
||||
protected LoginPasswordUpdatePage changePasswordPage;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
oauth.state("mystate"); // have to set this as keycloak validates that state is sent
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void tempPassword() throws Exception {
|
||||
loginPage.open();
|
||||
|
|
|
@ -145,6 +145,10 @@ public class LoginTotpTest {
|
|||
@Test
|
||||
public void loginWithTotpExpiredPasswordToken() throws Exception {
|
||||
try {
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
keycloakRule.configure(new KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
|
@ -153,9 +157,6 @@ public class LoginTotpTest {
|
|||
}
|
||||
});
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
loginTotpPage.assertCurrent();
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
@ -165,7 +166,11 @@ public class LoginTotpTest {
|
|||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).session((String) null).assertEvent();
|
||||
AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("invalid_user_credentials")
|
||||
.user((String)null)
|
||||
.clearDetails()
|
||||
.session((String) null);
|
||||
expectedEvent.assertEvent();
|
||||
} finally {
|
||||
keycloakRule.configure(new KeycloakSetup() {
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.services.protocol.OpenIdConnectProtocol;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
|
@ -73,7 +74,7 @@ public class UserSessionProviderTest {
|
|||
assertEquals(realm.findClient("test-app").getClientId(), session.getClient().getClientId());
|
||||
assertEquals(sessions[0].getId(), session.getUserSession().getId());
|
||||
assertEquals("http://redirect", session.getRedirectUri());
|
||||
assertEquals("state", session.getState());
|
||||
assertEquals("state", session.getNote(OpenIdConnectProtocol.STATE_PARAM));
|
||||
assertEquals(2, session.getRoles().size());
|
||||
assertTrue(session.getRoles().contains("one"));
|
||||
assertTrue(session.getRoles().contains("two"));
|
||||
|
@ -245,7 +246,11 @@ public class UserSessionProviderTest {
|
|||
for (int i = 0; i < 25; i++) {
|
||||
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false);
|
||||
userSession.setStarted(Time.currentTime() + i);
|
||||
session.sessions().createClientSession(realm, realm.findClient("test-app"), userSession, "http://redirect", "state", new HashSet<String>());
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.findClient("test-app"));
|
||||
clientSession.setUserSession(userSession);
|
||||
clientSession.setRedirectUri("http://redirect");
|
||||
clientSession.setRoles(new HashSet<String>());
|
||||
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, "state");
|
||||
}
|
||||
|
||||
resetSession();
|
||||
|
@ -280,6 +285,15 @@ public class UserSessionProviderTest {
|
|||
assertEquals(1, session.sessions().getActiveUserSessions(realm, realm.findClient("third-party")));
|
||||
}
|
||||
|
||||
private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles) {
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
if (userSession != null) clientSession.setUserSession(userSession);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
if (state != null) clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
|
||||
if (roles != null) clientSession.setRoles(roles);
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
private UserSessionModel[] createSessions() {
|
||||
UserSessionModel[] sessions = new UserSessionModel[3];
|
||||
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true);
|
||||
|
@ -288,14 +302,14 @@ public class UserSessionProviderTest {
|
|||
roles.add("one");
|
||||
roles.add("two");
|
||||
|
||||
session.sessions().createClientSession(realm, realm.findClient("test-app"), sessions[0], "http://redirect", "state", roles);
|
||||
session.sessions().createClientSession(realm, realm.findClient("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>());
|
||||
createClientSession(realm.findClient("test-app"), sessions[0], "http://redirect", "state", roles);
|
||||
createClientSession(realm.findClient("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>());
|
||||
|
||||
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true);
|
||||
session.sessions().createClientSession(realm, realm.findClient("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>());
|
||||
createClientSession(realm.findClient("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>());
|
||||
|
||||
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true);
|
||||
session.sessions().createClientSession(realm, realm.findClient("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>());
|
||||
createClientSession(realm.findClient("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>());
|
||||
|
||||
resetSession();
|
||||
|
||||
|
|
|
@ -135,7 +135,8 @@ public class AccessTokenTest {
|
|||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, "invalid");
|
||||
Assert.assertEquals(400, response.getStatusCode());
|
||||
|
||||
events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).assertEvent();
|
||||
AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID);
|
||||
expectedEvent.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -22,18 +22,23 @@
|
|||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.AccessCode;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AuthorizationCodeResponse;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
|
@ -65,6 +70,9 @@ public class AuthorizationCodeTest {
|
|||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@WebResource
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
|
@ -103,7 +111,7 @@ public class AuthorizationCodeTest {
|
|||
String code = driver.findElement(By.id(OAuth2Constants.CODE)).getText();
|
||||
keycloakRule.verifyCode(code);
|
||||
|
||||
String codeId = events.expectLogin().detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
|
||||
String codeId = events.expectLogin().detail(Details.REDIRECT_URI, "http://localhost:8081/auth/realms/test/tokens/oauth/oob").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
assertCode(codeId, code);
|
||||
|
||||
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||
|
@ -133,7 +141,10 @@ public class AuthorizationCodeTest {
|
|||
String error = driver.findElement(By.id(OAuth2Constants.ERROR)).getText();
|
||||
assertEquals("access_denied", error);
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null)
|
||||
.removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID)
|
||||
.detail(Details.REDIRECT_URI, "http://localhost:8081/auth/realms/test/tokens/oauth/oob")
|
||||
.assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
|
@ -167,22 +178,22 @@ public class AuthorizationCodeTest {
|
|||
|
||||
@Test
|
||||
public void authorizationRequestNoState() throws IOException {
|
||||
AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
|
||||
oauth.state(null);
|
||||
oauth.openLoginForm();
|
||||
Assert.assertTrue(errorPage.isCurrent());
|
||||
|
||||
Assert.assertTrue(response.isRedirected());
|
||||
Assert.assertNotNull(response.getCode());
|
||||
Assert.assertNull(response.getState());
|
||||
Assert.assertNull(response.getError());
|
||||
|
||||
keycloakRule.verifyCode(response.getCode());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
assertCode(codeId, response.getCode());
|
||||
events.expect(EventType.LOGIN_ERROR)
|
||||
.error(Errors.STATE_PARAM_NOT_FOUND)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REDIRECT_URI, oauth.getRedirectUri())
|
||||
.user((String)null)
|
||||
.assertEvent();
|
||||
//assertCode(codeId, response.getCode());
|
||||
}
|
||||
|
||||
private void assertCode(String expectedCodeId, String actualCode) {
|
||||
AccessCode code = keycloakRule.verifyCode(actualCode);
|
||||
assertEquals(expectedCodeId, code.getCodeId());
|
||||
ClientSessionCode code = keycloakRule.verifyCode(actualCode);
|
||||
assertEquals(expectedCodeId, code.getClientSession().getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -163,21 +165,24 @@ public class AccessTokenPerfTest {
|
|||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
static Pattern actionParser = Pattern.compile("action=\"([^\"]+)\"");
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//this.client = new ResteasyClientBuilder().build();
|
||||
String state = "42";
|
||||
Response response = client.target(getLoginFormUrl(state)).request().get();
|
||||
URI uri = null;
|
||||
if (200 == response.getStatus()) {
|
||||
response.close();
|
||||
String loginFormUrl = getLoginFormUrl(state);
|
||||
String html = client.target(loginFormUrl).request().get(String.class);
|
||||
Matcher matcher = actionParser.matcher(html);
|
||||
matcher.find();
|
||||
String actionUrl = matcher.group(1);
|
||||
if (!actionUrl.startsWith("http")) {
|
||||
actionUrl = UriBuilder.fromUri(actionUrl).scheme("http").host("localhost").port(8081).build().toString();
|
||||
}
|
||||
Form form = new Form();
|
||||
form.param("username", "test-user@localhost");
|
||||
form.param("password", "password");
|
||||
response = client.target(getProcessLoginUrl(state)).request().post(Entity.form(form));
|
||||
|
||||
}
|
||||
Response response = client.target(actionUrl).request().post(Entity.form(form));
|
||||
URI uri = null;
|
||||
Assert.assertEquals(302, response.getStatus());
|
||||
uri = response.getLocation();
|
||||
for (String header : response.getHeaders().keySet()) {
|
||||
|
@ -191,7 +196,7 @@ public class AccessTokenPerfTest {
|
|||
String code = getCode(uri);
|
||||
Assert.assertNotNull(code);
|
||||
|
||||
Form form = new Form();
|
||||
form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, grantType)
|
||||
.param(OAuth2Constants.CODE, code)
|
||||
.param(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.services.managers.AccessCode;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.ApplicationServlet;
|
||||
|
||||
|
@ -109,12 +110,12 @@ public class KeycloakRule extends AbstractKeycloakRule {
|
|||
stopSession(session, true);
|
||||
}
|
||||
|
||||
public AccessCode verifyCode(String code) {
|
||||
public ClientSessionCode verifyCode(String code) {
|
||||
KeycloakSession session = startSession();
|
||||
try {
|
||||
RealmModel realm = session.realms().getRealm("test");
|
||||
try {
|
||||
AccessCode accessCode = AccessCode.parse(code, session, realm);
|
||||
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
|
||||
if (accessCode == null) {
|
||||
Assert.fail("Invalid code");
|
||||
}
|
||||
|
|
|
@ -114,14 +114,17 @@ public class SocialLoginTest {
|
|||
String userId = events.expect(EventType.REGISTER)
|
||||
.user(AssertEvents.isUUID())
|
||||
.detail(Details.EMAIL, "bob@builder.com")
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REGISTER_METHOD, "social@dummy")
|
||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||
.detail(Details.USERNAME, "1@dummy")
|
||||
.session((String) null)
|
||||
.assertEvent().getUserId();
|
||||
|
||||
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent();
|
||||
Event loginEvent = events.expectSocialLogin()
|
||||
.user(userId)
|
||||
.detail(Details.USERNAME, "1@dummy")
|
||||
.detail(Details.AUTH_METHOD, "social@dummy")
|
||||
.assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
@ -153,7 +156,7 @@ public class SocialLoginTest {
|
|||
driver.findElement(By.id("username")).sendKeys("dummy-user1");
|
||||
driver.findElement(By.id("login")).click();
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent();
|
||||
events.expectSocialLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -199,8 +202,9 @@ public class SocialLoginTest {
|
|||
Assert.assertTrue(loginPage.isCurrent());
|
||||
Assert.assertEquals("Access denied", loginPage.getWarning());
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).detail(Details.AUTH_METHOD, "social@dummy").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
events.expectSocialLogin().error("rejected_by_user").user((String) null).session((String) null).detail(Details.AUTH_METHOD, "social@dummy").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
|
||||
String src = driver.getPageSource();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
@ -238,7 +242,6 @@ public class SocialLoginTest {
|
|||
String userId = events.expect(EventType.REGISTER)
|
||||
.user(AssertEvents.isUUID())
|
||||
.detail(Details.EMAIL, "bob@builder.com")
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REGISTER_METHOD, "social@dummy")
|
||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||
.detail(Details.USERNAME, "2@dummy")
|
||||
|
|
Loading…
Reference in a new issue