Merge pull request #711 from patriot1burke/master

cliensession refactor
This commit is contained in:
Bill Burke 2014-09-29 10:01:35 -04:00
commit 6c46af5b3c
59 changed files with 1645 additions and 677 deletions

View file

@ -21,6 +21,7 @@
<!-- JpaUserSessionProvider -->
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>

View file

@ -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";

View file

@ -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
}

View file

@ -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);

View file

@ -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;

View file

@ -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,19 @@ 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.
*
* @return
*/
public String getAuthMethod();
public void setAuthMethod(String method);
public String getNote(String name);
public void setNote(String name, String value);
public void removeNote(String name);
public static enum Action {
OAUTH_GRANT,
@ -33,7 +46,9 @@ public interface ClientSessionModel {
VERIFY_EMAIL,
UPDATE_PROFILE,
CONFIGURE_TOTP,
UPDATE_PASSWORD
UPDATE_PASSWORD,
AUTHENTICATE,
SOCIAL_CALLBACK
}
}

View file

@ -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);

View file

@ -6,10 +6,15 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
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;
/**
@ -29,6 +34,49 @@ 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()) {
if (attr.getName().equals(name)) {
attr.setValue(value);
return;
}
}
ClientSessionNoteEntity attr = new ClientSessionNoteEntity();
attr.setName(name);
attr.setValue(value);
attr.setClientSession(entity);
em.persist(attr);
entity.getNotes().add(attr);
}
@Override
public void removeNote(String name) {
Iterator<ClientSessionNoteEntity> it = entity.getNotes().iterator();
while (it.hasNext()) {
ClientSessionNoteEntity attr = it.next();
if (attr.getName().equals(name)) {
it.remove();
em.remove(attr);
}
}
}
@Override
public String getNote(String name) {
for (ClientSessionNoteEntity attr : entity.getNotes()) {
if (attr.getName().equals(name)) {
return attr.getValue();
}
}
return null;
}
@Override
public String getId() {
return entity.getId();
@ -40,12 +88,47 @@ 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
public String getAuthMethod() {
return entity.getAuthMethod();
}
@Override
public void setAuthMethod(String method) {
entity.setAuthMethod(method);
}
@Override
public UserSessionModel getUserSession() {
if (entity.getSession() == null) return null;
return new UserSessionAdapter(session, em, realm, entity.getSession());
}

View file

@ -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;
@ -185,6 +176,10 @@ public class JpaUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm, UserModel user) {
em.createNamedQuery("removeClientSessionNoteByUser")
.setParameter("realmId", realm.getId())
.setParameter("userId", user.getId())
.executeUpdate();
em.createNamedQuery("removeClientSessionRoleByUser")
.setParameter("realmId", realm.getId())
.setParameter("userId", user.getId())
@ -204,11 +199,28 @@ 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)
.setParameter("idleTime", idleTime)
.executeUpdate();
em.createNamedQuery("removeClientSessionNoteByExpired")
.setParameter("realmId", realm.getId())
.setParameter("maxTime", maxTime)
.setParameter("idleTime", idleTime)
.executeUpdate();
em.createNamedQuery("removeClientSessionByExpired")
.setParameter("realmId", realm.getId())
.setParameter("maxTime", maxTime)
@ -223,6 +235,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm) {
em.createNamedQuery("removeClientSessionNoteByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeClientSessionRoleByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeClientSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeUserSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
@ -236,6 +249,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
em.createNamedQuery("removeClientSessionNoteByClient").setParameter("realmId", realm.getId()).setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("removeClientSessionRoleByClient").setParameter("realmId", realm.getId()).setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("removeClientSessionByClient").setParameter("realmId", realm.getId()).setParameter("clientId", client.getId()).executeUpdate();
}

View file

@ -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,14 +42,17 @@ 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;
@Column(name="REDIRECT_URI")
protected String redirectUri;
@Column(name="STATE")
protected String state;
@Column(name="AUTH_METHOD")
protected String authMethod;
@Column(name="ACTION")
protected ClientSessionModel.Action action;
@ -56,6 +60,9 @@ public class ClientSessionEntity {
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
public String getId() {
return id;
}
@ -80,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;
}
@ -96,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;
}
@ -120,4 +127,19 @@ public class ClientSessionEntity {
this.roles = roles;
}
public Collection<ClientSessionNoteEntity> getNotes() {
return notes;
}
public void setNotes(Collection<ClientSessionNoteEntity> notes) {
this.notes = notes;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
}

View file

@ -0,0 +1,109 @@
package org.keycloak.models.sessions.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@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.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
@IdClass(ClientSessionNoteEntity.Key.class)
public class ClientSessionNoteEntity {
@Id
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name = "CLIENT_SESSION")
protected ClientSessionEntity clientSession;
@Id
@Column(name = "NAME")
protected String name;
@Column(name = "VALUE")
protected String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public ClientSessionEntity getClientSession() {
return clientSession;
}
public void setClientSession(ClientSessionEntity clientSession) {
this.clientSession = clientSession;
}
public static class Key implements Serializable {
protected ClientSessionEntity clientSession;
protected String name;
public Key() {
}
public Key(ClientSessionEntity clientSession, String name) {
this.clientSession = clientSession;
this.name = name;
}
public ClientSessionEntity getClientSession() {
return clientSession;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (name != null ? !name.equals(key.name) : key.name != null) return false;
if (clientSession != null ? !clientSession.getId().equals(key.clientSession != null ? key.clientSession.getId() : null) : key.clientSession != null) return false;
return true;
}
@Override
public int hashCode() {
int result = clientSession != null ? clientSession.getId().hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
}

View file

@ -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

View file

@ -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
@ -76,4 +98,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
return entity.getRoles();
}
@Override
public String getNote(String name) {
return entity.getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
entity.getNotes().put(name, value);
}
@Override
public void removeNote(String name) {
entity.getNotes().remove(name);
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
}
@Override
public void setAuthMethod(String method) {
entity.setAuthMethod(method);
}
}

View file

@ -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

View file

@ -30,6 +30,10 @@ public class UserSessionAdapter implements UserSessionModel {
this.entity = entity;
}
public UserSessionEntity getEntity() {
return entity;
}
public String getId() {
return entity.getId();
}

View file

@ -2,6 +2,8 @@ package org.keycloak.models.sessions.mem.entities;
import org.keycloak.models.ClientSessionModel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
@ -11,15 +13,17 @@ 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;
private ClientSessionModel.Action action;
private Set<String> roles;
private Map<String, String> notes = new HashMap<String, String>();
public String getId() {
return id;
@ -37,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;
}
@ -53,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;
}
@ -84,4 +88,16 @@ public class ClientSessionEntity {
public void setRoles(Set<String> roles) {
this.roles = roles;
}
public Map<String, String> getNotes() {
return notes;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
}

View file

@ -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
@ -85,4 +104,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
return entity.getRoles() != null ? new HashSet<String>(entity.getRoles()) : null;
}
@Override
public String getNote(String name) {
return entity.getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
entity.getNotes().put(name, value);
}
@Override
public void removeNote(String name) {
entity.getNotes().remove(name);
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
}
@Override
public void setAuthMethod(String method) {
entity.setAuthMethod(method);
}
}

View file

@ -38,50 +38,38 @@ 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()) {
return null;
@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);
}
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) {
MongoUserSessionEntity entity = new MongoUserSessionEntity();
@ -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

View file

@ -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;
}

View file

@ -1,23 +1,31 @@
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;
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;
private ClientSessionModel.Action action;
private List<String> roles;
private Map<String, String> notes = new HashMap<String, String>();
public String getId() {
return id;
@ -35,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;
}
@ -43,12 +59,12 @@ public class MongoClientSessionEntity {
this.redirectUri = redirectUri;
}
public String getState() {
return state;
public String getAuthMethod() {
return authMethod;
}
public void setState(String state) {
this.state = state;
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public int getTimestamp() {
@ -75,4 +91,24 @@ public class MongoClientSessionEntity {
this.roles = roles;
}
public Map<String, String> getNotes() {
return notes;
}
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) {
}
}

View file

@ -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);
}
}

View file

@ -14,6 +14,11 @@
<description/>
<dependencies>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-federation</artifactId>
<version>2.7.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>

View file

@ -103,7 +103,7 @@ public class AccessCode {
}
public String getState() {
return clientSession.getState();
throw new RuntimeException("REFACTORING, TODO REMOVE ACCESS CODE");
}
public String getRedirectUri() {

View file

@ -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));

View 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);
}
}
}

View file

@ -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>();

View file

@ -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";
}

View file

@ -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);

View file

@ -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,124 +231,174 @@ 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.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager).forwardToSecurityFailure(
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,
"Unknown login requester.");
}
if (!client.isEnabled()) {
return Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager).forwardToSecurityFailure(
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,
"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();
}
}

View file

@ -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,51 +108,37 @@ 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 oauth.forwardToSecurityFailure("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 oauth.forwardToSecurityFailure("Unknown login requester.");
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
return oauth.forwardToSecurityFailure("Login requester not enabled.");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
}
String key = realm.getSocialConfig().get(provider.getId() + ".key");
@ -159,26 +147,19 @@ public class SocialResource {
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 oauth.forwardToSecurityFailure("Failed to process social callback");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process social callback");
}
event.detail(Details.USERNAME, socialUser.getId() + "@" + provider.getId());
@ -188,37 +169,32 @@ 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);
return oauth.forwardToSecurityFailure("This social account is already linked to other user");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "This social account is already linked to other user");
}
if (!authenticatedUser.isEnabled()) {
event.error(Errors.USER_DISABLED);
return oauth.forwardToSecurityFailure("User is disabled");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "User is disabled");
}
if (!authenticatedUser.hasRole(realm.getApplicationByName(Constants.ACCOUNT_MANAGEMENT_APP).getRole(AccountRoles.MANAGE_ACCOUNT))) {
event.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("Insufficient permissions to link social account");
}
if (redirectUri == null) {
event.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Unknown redirectUri");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Insufficient permissions to link social account");
}
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) {
@ -245,38 +221,79 @@ public class SocialResource {
if (!user.isEnabled()) {
event.error(Errors.USER_DISABLED);
return oauth.forwardToSecurityFailure("Your account is not enabled.");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Your account is not enabled.");
}
String username = socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider();
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);
}
}
}

View file

@ -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,33 +513,19 @@ public class TokenService {
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
if (!checkSsl()) {
return oauth.forwardToSecurityFailure("HTTPS required");
}
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
return oauth.forwardToSecurityFailure("Realm not enabled.");
}
ClientModel client = realm.findClient(clientId);
ClientModel client = clientSession.getClient();
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
return oauth.forwardToSecurityFailure("Unknown login requester.");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_NOT_FOUND);
return oauth.forwardToSecurityFailure("Login requester not enabled.");
}
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
event.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
}
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)
@ -603,29 +646,19 @@ public class TokenService {
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
return oauth.forwardToSecurityFailure("Realm not enabled");
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 oauth.forwardToSecurityFailure("Unknown login requester.");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
return oauth.forwardToSecurityFailure("Login requester not enabled.");
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 oauth.forwardToSecurityFailure("Invalid redirect_uri.");
}
if (!realm.isRegistrationAllowed()) {
event.error(Errors.REGISTRATION_DISABLED);
return oauth.forwardToSecurityFailure("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>();
@ -892,12 +939,107 @@ public class TokenService {
return client;
}
/**
* checks input and initializes variables based on a front page action like the login page or registration page
*
*/
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);
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.");
}
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");
}
if (client.isDirectGrantsOnly()) {
event.error(Errors.NOT_ALLOWED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
}
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
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
@ -908,60 +1050,54 @@ public class TokenService {
*/
@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");
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);
if (!checkSsl()) {
return oauth.forwardToSecurityFailure("HTTPS required");
}
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
return oauth.forwardToSecurityFailure("Realm not enabled");
}
ClientModel client = realm.findClient(clientId);
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
return oauth.forwardToSecurityFailure("Unknown login requester.");
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
return oauth.forwardToSecurityFailure("Login requester not enabled.");
}
if ( (client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
event.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate browser login");
}
if (client.isDirectGrantsOnly()) {
event.error(Errors.NOT_ALLOWED);
return oauth.forwardToSecurityFailure("direct-grants-only clients are not allowed to initiate browser login");
}
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
event.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
}
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 oauth.forwardToSecurityFailure("HTTPS required");
}
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
return oauth.forwardToSecurityFailure("Realm not enabled");
}
ClientModel client = realm.findClient(clientId);
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
return oauth.forwardToSecurityFailure("Unknown login requester.");
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
return oauth.forwardToSecurityFailure("Login requester not enabled.");
}
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
if (redirect == null) {
event.error(Errors.INVALID_REDIRECT_URI);
return oauth.forwardToSecurityFailure("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 oauth.forwardToSecurityFailure("Registration not allowed");
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);
@ -1065,7 +1190,7 @@ public class TokenService {
String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm);
if (validatedRedirect == null) {
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
return oauth.forwardToSecurityFailure("Invalid redirect uri.");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
}
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
} else {
@ -1145,27 +1270,27 @@ public class TokenService {
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
if (!checkSsl()) {
return oauth.forwardToSecurityFailure("HTTPS required");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
}
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 oauth.forwardToSecurityFailure("Invalid access 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());
@ -1177,19 +1302,19 @@ public class TokenService {
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
event.error(Errors.INVALID_CODE);
return oauth.forwardToSecurityFailure("Session not active");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active");
}
event.session(userSession);
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")

View file

@ -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;
/**
@ -884,8 +888,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()) {
@ -894,13 +896,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());

View file

@ -22,4 +22,6 @@ public class ErrorFlows {
return Response.status(status).entity(error).type(MediaType.APPLICATION_JSON).build();
}
}

View file

@ -31,6 +31,7 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.social.SocialProvider;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
/**
@ -58,4 +59,9 @@ public class Flows {
return new ErrorFlows();
}
public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String message) {
return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage();
}
}

View file

@ -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,10 +249,13 @@ public class OAuthFlows {
return null;
}
}
*/
/*
public Response forwardToSecurityFailure(String message) {
return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage();
}
*/
private void isTotpConfigurationRequired(UserModel user) {
for (RequiredCredentialModel c : realm.getRequiredCredentials()) {

View file

@ -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();
}

View file

@ -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>

View file

@ -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")) {

View 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;
}
}

View 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);
}

View 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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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());
}
}

View file

@ -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();
Form form = new Form();
form.param("username", "test-user@localhost");
form.param("password", "password");
response = client.target(getProcessLoginUrl(state)).request().post(Entity.form(form));
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 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);

View file

@ -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");
}

View file

@ -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")