KEYCLOAK-389 Added AuditListener SPI
KEYCLOAK-390 Added JBoss Logging AuditListener KEYCLOAK-391 Audit Token events
This commit is contained in:
parent
911cf2ae45
commit
225307e855
58 changed files with 1859 additions and 165 deletions
39
audit/api/pom.xml
Executable file
39
audit/api/pom.xml
Executable file
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-audit-api</artifactId>
|
||||
<name>Keycloak Audit API</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<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>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
141
audit/api/src/main/java/org/keycloak/audit/Audit.java
Normal file
141
audit/api/src/main/java/org/keycloak/audit/Audit.java
Normal file
|
@ -0,0 +1,141 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Audit {
|
||||
|
||||
private static final Logger log = Logger.getLogger(Audit.class);
|
||||
|
||||
private List<AuditListener> listeners;
|
||||
private Event event;
|
||||
|
||||
public static Audit create(RealmModel realm, String ipAddress) {
|
||||
List<AuditListener> listeners = null;
|
||||
if (realm.getAuditListeners() != null) {
|
||||
listeners = new LinkedList<AuditListener>();
|
||||
|
||||
for (String id : realm.getAuditListeners()) {
|
||||
listeners.add(AuditLoader.load(id));
|
||||
}
|
||||
}
|
||||
return new Audit(listeners, new Event()).realm(realm).ipAddress(ipAddress);
|
||||
}
|
||||
|
||||
private Audit(List<AuditListener> listeners, Event event) {
|
||||
this.listeners = listeners;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public Audit realm(RealmModel realm) {
|
||||
event.setRealmId(realm.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit realm(String realmId) {
|
||||
event.setRealmId(realmId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit client(ClientModel client) {
|
||||
event.setClientId(client.getClientId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit client(String clientId) {
|
||||
event.setClientId(clientId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit user(UserModel user) {
|
||||
event.setUserId(user.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit user(String userId) {
|
||||
event.setUserId(userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit ipAddress(String ipAddress) {
|
||||
event.setIpAddress(ipAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit event(String e) {
|
||||
event.setEvent(e);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit detail(String key, String value) {
|
||||
if (value == null || value.equals("")) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (event.getDetails() == null) {
|
||||
event.setDetails(new HashMap<String, String>());
|
||||
}
|
||||
event.getDetails().put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit removeDetail(String key) {
|
||||
if (event.getDetails() != null) {
|
||||
event.getDetails().remove(key);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void success() {
|
||||
send();
|
||||
}
|
||||
|
||||
public void error(String error) {
|
||||
event.setError(error);
|
||||
send();
|
||||
}
|
||||
|
||||
public Audit clone() {
|
||||
return new Audit(listeners, event.clone());
|
||||
}
|
||||
|
||||
public Audit reset() {
|
||||
Event old = event;
|
||||
|
||||
event = new Event();
|
||||
event.setRealmId(old.getRealmId());
|
||||
event.setIpAddress(old.getIpAddress());
|
||||
event.setClientId(old.getClientId());
|
||||
event.setUserId(old.getUserId());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void send() {
|
||||
event.setTime(System.currentTimeMillis());
|
||||
|
||||
if (listeners != null) {
|
||||
for (AuditListener l : listeners) {
|
||||
try {
|
||||
l.onEvent(event);
|
||||
} catch (Throwable t) {
|
||||
log.error("Failed to send event to " + l, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface AuditListener {
|
||||
|
||||
public String getId();
|
||||
|
||||
public void onEvent(Event event);
|
||||
|
||||
}
|
31
audit/api/src/main/java/org/keycloak/audit/AuditLoader.java
Normal file
31
audit/api/src/main/java/org/keycloak/audit/AuditLoader.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
import org.keycloak.util.ProviderLoader;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AuditLoader {
|
||||
|
||||
private AuditLoader() {
|
||||
}
|
||||
|
||||
public static AuditListener load(String id) {
|
||||
if (id == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
for (AuditListener l : load()) {
|
||||
if (id.equals(l.getId())) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Iterable<AuditListener> load() {
|
||||
return ProviderLoader.load(AuditListener.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface AuditProvider extends AuditListener {
|
||||
|
||||
public EventQuery createQuery();
|
||||
|
||||
}
|
22
audit/api/src/main/java/org/keycloak/audit/Details.java
Normal file
22
audit/api/src/main/java/org/keycloak/audit/Details.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface Details {
|
||||
|
||||
String EMAIL = "email";
|
||||
String PREVIOUS_EMAIL = "previous_email";
|
||||
String UPDATED_EMAIL = "updated_email";
|
||||
String CODE_ID = "code_id";
|
||||
String REDIRECT_URI = "redirect_uri";
|
||||
String RESPONSE_TYPE = "response_type";
|
||||
String AUTH_METHOD = "auth_method";
|
||||
String REGISTER_METHOD = "register_method";
|
||||
String USERNAME = "username";
|
||||
String REMEMBER_ME = "remember_me";
|
||||
String TOKEN_ID = "token_id";
|
||||
String REFRESH_TOKEN_ID = "refresh_token_id";
|
||||
String UPDATED_REFRESH_TOKEN_ID = "updated_refresh_token_id";
|
||||
|
||||
}
|
36
audit/api/src/main/java/org/keycloak/audit/Errors.java
Normal file
36
audit/api/src/main/java/org/keycloak/audit/Errors.java
Normal file
|
@ -0,0 +1,36 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface Errors {
|
||||
|
||||
String REALM_DISABLED = "realm_disabled";
|
||||
|
||||
String CLIENT_NOT_FOUND = "client_not_found";
|
||||
String CLIENT_DISABLED = "client_disabled";
|
||||
String INVALID_CLIENT_CREDENTIALS = "invalid_client_credentials";
|
||||
|
||||
String USER_NOT_FOUND = "user_not_found";
|
||||
String USER_DISABLED = "user_disabled";
|
||||
String INVALID_USER_CREDENTIALS = "invalid_user_credentials";
|
||||
|
||||
String USERNAME_MISSING = "username_missing";
|
||||
String USERNAME_IN_USE = "username_in_use";
|
||||
|
||||
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
||||
String INVALID_CODE = "invalid_code";
|
||||
String INVALID_TOKEN = "invalid_token";
|
||||
String INVALID_REGISTRATION = "invalid_registration";
|
||||
String INVALID_FORM = "invalid_form";
|
||||
|
||||
String REGISTRATION_DISABLED = "registration_disabled";
|
||||
|
||||
String REJECTED_BY_USER = "rejected_by_user";
|
||||
|
||||
String NOT_ALLOWED = "not_allowed";
|
||||
|
||||
String SOCIAL_PROVIDER_NOT_FOUND = "social_provider_not_found";
|
||||
String SOCIAL_ID_IN_USE = "social_id_in_use";
|
||||
|
||||
}
|
108
audit/api/src/main/java/org/keycloak/audit/Event.java
Normal file
108
audit/api/src/main/java/org/keycloak/audit/Event.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Event {
|
||||
|
||||
private long time;
|
||||
|
||||
private String event;
|
||||
|
||||
private String realmId;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
private String error;
|
||||
|
||||
private Map<String, String> details;
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(String event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error != null;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public Event clone() {
|
||||
Event clone = new Event();
|
||||
clone.time = time;
|
||||
clone.event = event;
|
||||
clone.realmId = realmId;
|
||||
clone.clientId = clientId;
|
||||
clone.userId = userId;
|
||||
clone.ipAddress = ipAddress;
|
||||
clone.error = error;
|
||||
clone.details = details != null ? new HashMap<String, String>(details) : null;
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
24
audit/api/src/main/java/org/keycloak/audit/EventQuery.java
Normal file
24
audit/api/src/main/java/org/keycloak/audit/EventQuery.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface EventQuery {
|
||||
|
||||
public EventQuery event(String event);
|
||||
|
||||
public EventQuery realm(String realmId);
|
||||
|
||||
public EventQuery client(String clientId);
|
||||
|
||||
public EventQuery user(String userId);
|
||||
|
||||
public EventQuery firstResult(int result);
|
||||
|
||||
public EventQuery maxResults(int results);
|
||||
|
||||
public List<Event> getResultList();
|
||||
|
||||
}
|
29
audit/api/src/main/java/org/keycloak/audit/Events.java
Normal file
29
audit/api/src/main/java/org/keycloak/audit/Events.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface Events {
|
||||
|
||||
String LOGIN = "login";
|
||||
String REGISTER = "register";
|
||||
String LOGOUT = "logout";
|
||||
String CODE_TO_TOKEN = "code_to_token";
|
||||
String REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
String SOCIAL_LINK = "social_link";
|
||||
String REMOVE_SOCIAL_LINK = "remove_social_link";
|
||||
|
||||
String UPDATE_EMAIL = "update_email";
|
||||
String UPDATE_PROFILE = "update_profile";
|
||||
String UPDATE_PASSWORD = "update_password";
|
||||
String UPDATE_TOTP = "update_totp";
|
||||
|
||||
String VERIFY_EMAIL = "verify_email";
|
||||
|
||||
String REMOVE_TOTP = "remove_totp";
|
||||
|
||||
String SEND_VERIFY_EMAIL = "send_verify_email";
|
||||
String SEND_RESET_PASSWORD = "send_reset_password";
|
||||
|
||||
}
|
33
audit/jboss-logging/pom.xml
Executable file
33
audit/jboss-logging/pom.xml
Executable file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-audit-jboss-logging</artifactId>
|
||||
<name>Keycloak Audit JBoss Logging Provider</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,63 @@
|
|||
package org.keycloak.audit.log;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.audit.Event;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JBossLoggingAuditListener implements AuditListener {
|
||||
|
||||
private static final Logger logger = Logger.getLogger("org.keycloak.audit");
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "jboss-logging";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
Logger.Level level = event.isError() ? Logger.Level.WARN : Logger.Level.INFO;
|
||||
|
||||
if (logger.isEnabled(level)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("event=");
|
||||
sb.append(event.getEvent());
|
||||
sb.append(", realmId=");
|
||||
sb.append(event.getRealmId());
|
||||
sb.append(", clientId=");
|
||||
sb.append(event.getClientId());
|
||||
sb.append(", userId=");
|
||||
sb.append(event.getUserId());
|
||||
sb.append(", ipAddress=");
|
||||
sb.append(event.getIpAddress());
|
||||
|
||||
if (event.isError()) {
|
||||
sb.append(", error=");
|
||||
sb.append(event.getError());
|
||||
}
|
||||
|
||||
if (event.getDetails() != null) {
|
||||
for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
|
||||
sb.append(", ");
|
||||
sb.append(e.getKey());
|
||||
if (e.getValue() == null || e.getValue().indexOf(' ') == -1) {
|
||||
sb.append("=");
|
||||
sb.append(e.getValue());
|
||||
} else {
|
||||
sb.append("='");
|
||||
sb.append(e.getValue());
|
||||
sb.append("'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(level, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.audit.log.JBossLoggingAuditListener
|
29
audit/jpa/pom.xml
Executable file
29
audit/jpa/pom.xml
Executable file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-audit-jpa</artifactId>
|
||||
<name>Keycloak Audit JPA Provider</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
23
audit/pom.xml
Executable file
23
audit/pom.xml
Executable file
|
@ -0,0 +1,23 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<name>Audit Parent</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>jpa</module>
|
||||
<module>jboss-logging</module>
|
||||
</modules>
|
||||
</project>
|
|
@ -202,4 +202,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
|||
void setNotBefore(int notBefore);
|
||||
|
||||
boolean removeRoleById(String id);
|
||||
|
||||
Set<String> getAuditListeners();
|
||||
|
||||
void setAuditListeners(Set<String> listeners);
|
||||
}
|
||||
|
|
|
@ -1154,4 +1154,15 @@ public class RealmAdapter implements RealmModel {
|
|||
realm.setAccountTheme(name);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAuditListeners() {
|
||||
return realm.getAuditListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuditListeners(Set<String> listeners) {
|
||||
realm.setAuditListeners(listeners);
|
||||
em.flush();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@ import javax.persistence.OneToMany;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -95,6 +97,9 @@ public class RealmEntity {
|
|||
@JoinTable(name="RealmDefaultRoles")
|
||||
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
|
||||
|
||||
@ElementCollection
|
||||
protected Set<String> auditListeners= new HashSet<String>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -333,5 +338,13 @@ public class RealmEntity {
|
|||
public void setNotBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
public Set<String> getAuditListeners() {
|
||||
return auditListeners;
|
||||
}
|
||||
|
||||
public void setAuditListeners(Set<String> auditListeners) {
|
||||
this.auditListeners = auditListeners;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -1113,6 +1114,20 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
|||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAuditListeners() {
|
||||
return realm.getAuditListeners() != null ? new HashSet<String>(realm.getAuditListeners()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuditListeners(Set<String> listeners) {
|
||||
if (listeners != null) {
|
||||
realm.setAuditListeners(new LinkedList<String>(listeners));
|
||||
} else {
|
||||
realm.setAuditListeners(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmEntity getMongoEntity() {
|
||||
return realm;
|
||||
|
|
|
@ -10,8 +10,11 @@ import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -53,6 +56,8 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
|||
private Map<String, String> socialConfig = new HashMap<String, String>();
|
||||
private Map<String, String> ldapServerConfig;
|
||||
|
||||
private List<String> auditListeners = new LinkedList<String>();
|
||||
|
||||
@MongoField
|
||||
public String getName() {
|
||||
return name;
|
||||
|
@ -287,6 +292,15 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
|||
this.ldapServerConfig = ldapServerConfig;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getAuditListeners() {
|
||||
return auditListeners;
|
||||
}
|
||||
|
||||
public void setAuditListeners(List<String> auditListeners) {
|
||||
this.auditListeners = auditListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
DBObject query = new QueryBuilder()
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<version>1.0-beta-1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Examples</name>
|
||||
<name>Model Parent</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -80,6 +80,7 @@
|
|||
</contributors>
|
||||
|
||||
<modules>
|
||||
<module>audit</module>
|
||||
<module>core</module>
|
||||
<module>core-jaxrs</module>
|
||||
<module>model</module>
|
||||
|
|
|
@ -36,6 +36,12 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-account-api</artifactId>
|
||||
|
|
|
@ -25,6 +25,8 @@ public class AccessCodeEntry {
|
|||
protected String state;
|
||||
protected String redirectUri;
|
||||
protected boolean rememberMe;
|
||||
protected String authMethod;
|
||||
protected String username;
|
||||
|
||||
protected int expiration;
|
||||
protected RealmModel realm;
|
||||
|
@ -130,4 +132,20 @@ public class AccessCodeEntry {
|
|||
public void setRememberMe(boolean rememberMe) {
|
||||
this.rememberMe = rememberMe;
|
||||
}
|
||||
|
||||
public String getAuthMethod() {
|
||||
return authMethod;
|
||||
}
|
||||
|
||||
public void setAuthMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -61,6 +63,8 @@ public class ApplianceBootstrap {
|
|||
adminConsole.setBaseUrl("/auth/admin/index.html");
|
||||
adminConsole.setEnabled(true);
|
||||
|
||||
realm.setAuditListeners(Collections.singleton("jboss-logging"));
|
||||
|
||||
RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
|
||||
|
||||
adminConsole.addScope(adminRole);
|
||||
|
|
|
@ -81,6 +81,8 @@ public class RealmManager {
|
|||
setupAdminManagement(realm);
|
||||
setupAccountManagement(realm);
|
||||
|
||||
realm.setAuditListeners(Collections.singleton("jboss-logging"));
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package org.keycloak.services.managers;
|
|||
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
|
@ -98,7 +100,7 @@ public class TokenManager {
|
|||
return code;
|
||||
}
|
||||
|
||||
public AccessToken refreshAccessToken(RealmModel realm, ClientModel client, String encodedRefreshToken) throws OAuthErrorException {
|
||||
public AccessToken refreshAccessToken(RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
|
||||
JWSInput jws = new JWSInput(encodedRefreshToken);
|
||||
RefreshToken refreshToken = null;
|
||||
try {
|
||||
|
@ -117,6 +119,8 @@ public class TokenManager {
|
|||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
|
||||
}
|
||||
|
||||
audit.user(refreshToken.getSubject()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
|
||||
|
||||
UserModel user = realm.getUserById(refreshToken.getSubject());
|
||||
if (user == null) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
|
||||
|
@ -320,8 +324,8 @@ public class TokenManager {
|
|||
return encodedToken;
|
||||
}
|
||||
|
||||
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client) {
|
||||
return new AccessTokenResponseBuilder(realm, client);
|
||||
public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, Audit audit) {
|
||||
return new AccessTokenResponseBuilder(realm, client, audit);
|
||||
}
|
||||
|
||||
public class AccessTokenResponseBuilder {
|
||||
|
@ -330,10 +334,12 @@ public class TokenManager {
|
|||
AccessToken accessToken;
|
||||
RefreshToken refreshToken;
|
||||
IDToken idToken;
|
||||
Audit audit;
|
||||
|
||||
public AccessTokenResponseBuilder(RealmModel realm, ClientModel client) {
|
||||
public AccessTokenResponseBuilder(RealmModel realm, ClientModel client, Audit audit) {
|
||||
this.realm = realm;
|
||||
this.client = client;
|
||||
this.audit = audit;
|
||||
}
|
||||
|
||||
public AccessTokenResponseBuilder accessToken(AccessToken accessToken) {
|
||||
|
@ -402,7 +408,21 @@ public class TokenManager {
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AccessTokenResponse build() {
|
||||
if (accessToken != null) {
|
||||
audit.detail(Details.TOKEN_ID, accessToken.getId());
|
||||
}
|
||||
|
||||
if (refreshToken != null) {
|
||||
if (audit.getEvent().getDetails().containsKey(Details.REFRESH_TOKEN_ID)) {
|
||||
audit.detail(Details.UPDATED_REFRESH_TOKEN_ID, refreshToken.getId());
|
||||
} else {
|
||||
audit.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
|
||||
}
|
||||
}
|
||||
|
||||
AccessTokenResponse res = new AccessTokenResponse();
|
||||
if (idToken != null) {
|
||||
String encodedToken = new JWSBuilder().jsonContent(idToken).rsa256(realm.getPrivateKey());
|
||||
|
|
|
@ -27,10 +27,14 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.account.Account;
|
||||
import org.keycloak.account.AccountLoader;
|
||||
import org.keycloak.account.AccountPages;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Events;
|
||||
import org.keycloak.jaxrs.JaxrsOAuthClient;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.AccessCodeEntry;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.ModelToRepresentation;
|
||||
|
@ -75,11 +79,13 @@ public class AccountService {
|
|||
|
||||
private final AppAuthManager authManager;
|
||||
private final ApplicationModel application;
|
||||
private Audit audit;
|
||||
private final SocialRequestManager socialRequestManager;
|
||||
|
||||
public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager, SocialRequestManager socialRequestManager) {
|
||||
public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager, SocialRequestManager socialRequestManager, Audit audit) {
|
||||
this.realm = realm;
|
||||
this.application = application;
|
||||
this.audit = audit;
|
||||
this.authManager = new AppAuthManager(KEYCLOAK_ACCOUNT_IDENTITY_COOKIE, tokenManager);
|
||||
this.socialRequestManager = socialRequestManager;
|
||||
}
|
||||
|
@ -170,8 +176,20 @@ public class AccountService {
|
|||
|
||||
user.setFirstName(formData.getFirst("firstName"));
|
||||
user.setLastName(formData.getFirst("lastName"));
|
||||
|
||||
String email = formData.getFirst("email");
|
||||
String oldEmail = user.getEmail();
|
||||
boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
|
||||
|
||||
user.setEmail(formData.getFirst("email"));
|
||||
|
||||
audit.event(Events.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
if (emailChanged) {
|
||||
user.setEmailVerified(false);
|
||||
audit.clone().event(Events.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
||||
}
|
||||
|
||||
return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT);
|
||||
}
|
||||
|
||||
|
@ -184,6 +202,8 @@ public class AccountService {
|
|||
UserModel user = auth.getUser();
|
||||
user.setTotp(false);
|
||||
|
||||
audit.event(Events.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
|
||||
return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP);
|
||||
}
|
||||
|
@ -215,6 +235,8 @@ public class AccountService {
|
|||
|
||||
user.setTotp(true);
|
||||
|
||||
audit.event(Events.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
return account.setSuccess("successTotp").createResponse(AccountPages.TOTP);
|
||||
}
|
||||
|
||||
|
@ -253,6 +275,8 @@ public class AccountService {
|
|||
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
||||
audit.event(Events.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
|
||||
}
|
||||
|
||||
|
@ -298,8 +322,16 @@ public class AccountService {
|
|||
return account.setError(Messages.SOCIAL_REDIRECT_ERROR).createResponse(AccountPages.SOCIAL);
|
||||
}
|
||||
case REMOVE:
|
||||
if (realm.removeSocialLink(user, providerId)) {
|
||||
SocialLinkModel link = realm.getSocialLink(user, providerId);
|
||||
if (link != null) {
|
||||
realm.removeSocialLink(user, providerId);
|
||||
|
||||
logger.debug("Social provider " + providerId + " removed successfully from user " + user.getLoginName());
|
||||
|
||||
audit.event(Events.REMOVE_SOCIAL_LINK).client(auth.getClient()).user(auth.getUser())
|
||||
.detail(Details.USERNAME, link.getSocialUserId() + "@" + link.getSocialProvider())
|
||||
.success();
|
||||
|
||||
return account.setSuccess(Messages.SOCIAL_PROVIDER_REMOVED).createResponse(AccountPages.SOCIAL);
|
||||
} else {
|
||||
return account.setError(Messages.SOCIAL_LINK_NOT_ACTIVE).createResponse(AccountPages.SOCIAL);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -9,6 +10,7 @@ import org.keycloak.services.managers.RealmManager;
|
|||
import org.keycloak.services.managers.SocialRequestManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
|
@ -38,6 +40,9 @@ public class RealmsResource {
|
|||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected HttpServletRequest servletRequest;
|
||||
|
||||
protected TokenManager tokenManager;
|
||||
protected SocialRequestManager socialRequestManager;
|
||||
|
||||
|
@ -54,7 +59,8 @@ public class RealmsResource {
|
|||
public TokenService getTokenService(final @PathParam("realm") String name) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = locateRealm(name, realmManager);
|
||||
TokenService tokenService = new TokenService(realm, tokenManager);
|
||||
Audit audit = Audit.create(realm, servletRequest.getRemoteAddr());
|
||||
TokenService tokenService = new TokenService(realm, tokenManager, audit);
|
||||
resourceContext.initResource(tokenService);
|
||||
return tokenService;
|
||||
}
|
||||
|
@ -78,7 +84,9 @@ public class RealmsResource {
|
|||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
AccountService accountService = new AccountService(realm, application, tokenManager, socialRequestManager);
|
||||
Audit audit = Audit.create(realm, servletRequest.getRemoteAddr());
|
||||
|
||||
AccountService accountService = new AccountService(realm, application, tokenManager, socialRequestManager, audit);
|
||||
resourceContext.initResource(accountService);
|
||||
return accountService;
|
||||
}
|
||||
|
@ -92,5 +100,4 @@ public class RealmsResource {
|
|||
return realmResource;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ package org.keycloak.services.resources;
|
|||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Errors;
|
||||
import org.keycloak.audit.Events;
|
||||
import org.keycloak.login.LoginForms;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
|
@ -84,9 +88,12 @@ public class RequiredActionsService {
|
|||
|
||||
private TokenManager tokenManager;
|
||||
|
||||
public RequiredActionsService(RealmModel realm, TokenManager tokenManager) {
|
||||
private Audit audit;
|
||||
|
||||
public RequiredActionsService(RealmModel realm, TokenManager tokenManager, Audit audit) {
|
||||
this.realm = realm;
|
||||
this.tokenManager = tokenManager;
|
||||
this.audit = audit;
|
||||
}
|
||||
|
||||
@Path("profile")
|
||||
|
@ -100,6 +107,8 @@ public class RequiredActionsService {
|
|||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initAudit(accessCode);
|
||||
|
||||
String error = Validation.validateUpdateProfileForm(formData);
|
||||
if (error != null) {
|
||||
return Flows.forms(realm, request, uriInfo).setUser(user).setError(error).createResponse(RequiredAction.UPDATE_PROFILE);
|
||||
|
@ -107,11 +116,22 @@ public class RequiredActionsService {
|
|||
|
||||
user.setFirstName(formData.getFirst("firstName"));
|
||||
user.setLastName(formData.getFirst("lastName"));
|
||||
user.setEmail(formData.getFirst("email"));
|
||||
|
||||
String email = formData.getFirst("email");
|
||||
String oldEmail = user.getEmail();
|
||||
boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
|
||||
|
||||
user.setEmail(email);
|
||||
|
||||
user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
|
||||
accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PROFILE);
|
||||
|
||||
audit.clone().event(Events.UPDATE_PROFILE).success();
|
||||
if (emailChanged) {
|
||||
user.setEmailVerified(false);
|
||||
audit.clone().event(Events.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
||||
}
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
}
|
||||
|
||||
|
@ -126,6 +146,8 @@ public class RequiredActionsService {
|
|||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initAudit(accessCode);
|
||||
|
||||
String totp = formData.getFirst("totp");
|
||||
String totpSecret = formData.getFirst("totpSecret");
|
||||
|
||||
|
@ -146,6 +168,8 @@ public class RequiredActionsService {
|
|||
user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||
accessCode.getRequiredActions().remove(RequiredAction.CONFIGURE_TOTP);
|
||||
|
||||
audit.clone().event(Events.UPDATE_TOTP).success();
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
}
|
||||
|
||||
|
@ -163,6 +187,8 @@ public class RequiredActionsService {
|
|||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initAudit(accessCode);
|
||||
|
||||
String passwordNew = formData.getFirst("password-new");
|
||||
String passwordConfirm = formData.getFirst("password-confirm");
|
||||
|
||||
|
@ -186,6 +212,8 @@ public class RequiredActionsService {
|
|||
accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
|
||||
}
|
||||
|
||||
audit.clone().event(Events.UPDATE_PASSWORD).success();
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
}
|
||||
|
||||
|
@ -201,11 +229,16 @@ public class RequiredActionsService {
|
|||
}
|
||||
|
||||
UserModel user = getUser(accessCode);
|
||||
|
||||
initAudit(accessCode);
|
||||
|
||||
user.setEmailVerified(true);
|
||||
|
||||
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
|
||||
accessCode.getRequiredActions().remove(RequiredAction.VERIFY_EMAIL);
|
||||
|
||||
audit.clone().event(Events.VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
|
||||
|
||||
return redirectOauth(user, accessCode);
|
||||
} else {
|
||||
AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
|
||||
|
@ -213,6 +246,9 @@ public class RequiredActionsService {
|
|||
return unauthorized();
|
||||
}
|
||||
|
||||
initAudit(accessCode);
|
||||
//audit.clone().event(Events.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
|
||||
|
||||
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(accessCode.getUser())
|
||||
.createResponse(RequiredAction.VERIFY_EMAIL);
|
||||
}
|
||||
|
@ -223,10 +259,12 @@ public class RequiredActionsService {
|
|||
public Response passwordReset() {
|
||||
if (uriInfo.getQueryParameters().containsKey("key")) {
|
||||
AccessCodeEntry accessCode = tokenManager.getAccessCode(uriInfo.getQueryParameters().getFirst("key"));
|
||||
accessCode.setAuthMethod("form");
|
||||
if (accessCode == null || accessCode.isExpired()
|
||||
|| !accessCode.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD)) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||
} else {
|
||||
return Flows.forms(realm, request, uriInfo).createPasswordReset();
|
||||
|
@ -254,6 +292,12 @@ public class RequiredActionsService {
|
|||
"Login requester not enabled.");
|
||||
}
|
||||
|
||||
audit.event(Events.SEND_RESET_PASSWORD).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.USERNAME, username);
|
||||
|
||||
UserModel user = realm.getUser(username);
|
||||
if (user == null && username.contains("@")) {
|
||||
user = realm.getUserByEmail(username);
|
||||
|
@ -261,6 +305,7 @@ public class RequiredActionsService {
|
|||
|
||||
if (user == null) {
|
||||
logger.warn("Failed to send password reset email: user not found");
|
||||
audit.error(Errors.USER_NOT_FOUND);
|
||||
} else {
|
||||
Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
|
||||
requiredActions.add(RequiredAction.UPDATE_PASSWORD);
|
||||
|
@ -268,9 +313,12 @@ public class RequiredActionsService {
|
|||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
|
||||
accessCode.setRequiredActions(requiredActions);
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
accessCode.setAuthMethod("form");
|
||||
accessCode.setUsername(username);
|
||||
|
||||
try {
|
||||
new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
|
||||
audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getId()).success();
|
||||
} catch (EmailException e) {
|
||||
logger.error("Failed to send password reset email", e);
|
||||
return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
|
||||
|
@ -339,11 +387,27 @@ public class RequiredActionsService {
|
|||
} else {
|
||||
logger.debug("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
|
||||
audit.success();
|
||||
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
|
||||
accessCode.getState(), accessCode.getRedirectUri());
|
||||
}
|
||||
}
|
||||
|
||||
private void initAudit(AccessCodeEntry accessCode) {
|
||||
audit.event(Events.LOGIN).client(accessCode.getClient())
|
||||
.user(accessCode.getUser())
|
||||
.detail(Details.CODE_ID, accessCode.getId())
|
||||
.detail(Details.REDIRECT_URI, accessCode.getRedirectUri())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, accessCode.getAuthMethod())
|
||||
.detail(Details.USERNAME, accessCode.getUsername());
|
||||
|
||||
if (accessCode.isRememberMe()) {
|
||||
audit.detail(Details.REMEMBER_ME, "true");
|
||||
}
|
||||
}
|
||||
|
||||
private Response unauthorized() {
|
||||
return Flows.forms(realm, request, uriInfo).setError("Unauthorized request").createErrorPage();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ package org.keycloak.services.resources;
|
|||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Errors;
|
||||
import org.keycloak.audit.Events;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
|
@ -48,6 +52,7 @@ import org.keycloak.social.SocialProviderConfig;
|
|||
import org.keycloak.social.SocialProviderException;
|
||||
import org.keycloak.social.SocialUser;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
|
@ -89,6 +94,8 @@ public class SocialResource {
|
|||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected HttpServletRequest servletRequest;
|
||||
|
||||
private SocialRequestManager socialRequestManager;
|
||||
|
||||
|
@ -114,19 +121,33 @@ public class SocialResource {
|
|||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
|
||||
Audit audit = Audit.create(realm, servletRequest.getRemoteAddr())
|
||||
.event(Events.LOGIN)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "social");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Realm not enabled.");
|
||||
}
|
||||
|
||||
String clientId = requestData.getClientAttributes().get("clientId");
|
||||
String redirectUri = requestData.getClientAttribute("redirectUri");
|
||||
String scope = requestData.getClientAttributes().get(OAuth2Constants.SCOPE);
|
||||
String state = requestData.getClientAttributes().get(OAuth2Constants.STATE);
|
||||
String responseType = requestData.getClientAttribute("responseType");
|
||||
|
||||
audit.client(clientId).detail(Details.REDIRECT_URI, redirectUri);
|
||||
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
return oauth.forwardToSecurityFailure("Unknown login requester.");
|
||||
}
|
||||
if (!client.isEnabled()) {
|
||||
audit.error(Errors.CLIENT_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||
}
|
||||
|
||||
|
@ -142,17 +163,21 @@ public class SocialResource {
|
|||
socialUser = provider.processCallback(config, callback);
|
||||
} catch (SocialAccessDeniedException e) {
|
||||
MultivaluedHashMap<String, String> queryParms = new MultivaluedHashMap<String, String>();
|
||||
queryParms.putSingle(OAuth2Constants.CLIENT_ID, requestData.getClientAttribute("clientId"));
|
||||
queryParms.putSingle(OAuth2Constants.STATE, requestData.getClientAttribute(OAuth2Constants.STATE));
|
||||
queryParms.putSingle(OAuth2Constants.SCOPE, requestData.getClientAttribute(OAuth2Constants.SCOPE));
|
||||
queryParms.putSingle(OAuth2Constants.REDIRECT_URI, requestData.getClientAttribute("redirectUri"));
|
||||
queryParms.putSingle(OAuth2Constants.RESPONSE_TYPE, requestData.getClientAttribute("responseType"));
|
||||
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);
|
||||
|
||||
audit.error(Errors.REJECTED_BY_USER);
|
||||
return Flows.forms(realm, request, uriInfo).setQueryParams(queryParms).setWarning("Access denied").createLogin();
|
||||
} catch (SocialProviderException e) {
|
||||
logger.warn("Failed to process social callback", e);
|
||||
logger.error("Failed to process social callback", e);
|
||||
return oauth.forwardToSecurityFailure("Failed to process social callback");
|
||||
}
|
||||
|
||||
audit.detail(Details.USERNAME, socialUser.getId() + "@" + provider.getId());
|
||||
|
||||
SocialLinkModel socialLink = new SocialLinkModel(provider.getId(), socialUser.getId(), socialUser.getUsername());
|
||||
UserModel user = realm.getUserBySocialLink(socialLink);
|
||||
|
||||
|
@ -161,30 +186,39 @@ public class SocialResource {
|
|||
if (userId != null) {
|
||||
UserModel authenticatedUser = realm.getUserById(userId);
|
||||
|
||||
audit.event(Events.SOCIAL_LINK).user(userId);
|
||||
|
||||
if (user != null) {
|
||||
audit.error(Errors.SOCIAL_ID_IN_USE);
|
||||
return oauth.forwardToSecurityFailure("This social account is already linked to other user");
|
||||
}
|
||||
|
||||
if (!authenticatedUser.isEnabled()) {
|
||||
audit.error(Errors.USER_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("User is disabled");
|
||||
}
|
||||
|
||||
if (!realm.hasRole(authenticatedUser, realm.getApplicationByName(Constants.ACCOUNT_MANAGEMENT_APP).getRole(AccountRoles.MANAGE_ACCOUNT))) {
|
||||
audit.error(Errors.NOT_ALLOWED);
|
||||
return oauth.forwardToSecurityFailure("Insufficient permissions to link social account");
|
||||
}
|
||||
|
||||
if (redirectUri == null) {
|
||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||
return oauth.forwardToSecurityFailure("Unknown redirectUri");
|
||||
}
|
||||
|
||||
realm.addSocialLink(authenticatedUser, socialLink);
|
||||
logger.debug("Social provider " + provider.getId() + " linked with user " + authenticatedUser.getLoginName());
|
||||
|
||||
String redirectUri = requestData.getClientAttributes().get("redirectUri");
|
||||
if (redirectUri == null) {
|
||||
return oauth.forwardToSecurityFailure("Unknown redirectUri");
|
||||
}
|
||||
|
||||
audit.success();
|
||||
return Response.status(Status.FOUND).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
audit.error(Errors.REGISTRATION_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Registration not allowed");
|
||||
}
|
||||
|
||||
|
@ -199,17 +233,22 @@ public class SocialResource {
|
|||
}
|
||||
|
||||
realm.addSocialLink(user, socialLink);
|
||||
|
||||
audit.clone().user(user).event(Events.REGISTER)
|
||||
.detail(Details.REGISTER_METHOD, "social")
|
||||
.detail(Details.EMAIL, socialUser.getEmail())
|
||||
.removeDetail("auth_method")
|
||||
.success();
|
||||
}
|
||||
|
||||
audit.user(user);
|
||||
|
||||
if (!user.isEnabled()) {
|
||||
audit.error(Errors.USER_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Your account is not enabled.");
|
||||
}
|
||||
|
||||
String scope = requestData.getClientAttributes().get(OAuth2Constants.SCOPE);
|
||||
String state = requestData.getClientAttributes().get(OAuth2Constants.STATE);
|
||||
String redirectUri = requestData.getClientAttributes().get("redirectUri");
|
||||
|
||||
return oauth.processAccessCode(scope, state, redirectUri, client, user);
|
||||
return oauth.processAccessCode(scope, state, redirectUri, client, user, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social", audit);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -221,23 +260,33 @@ public class SocialResource {
|
|||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
|
||||
Audit audit = Audit.create(realm, servletRequest.getRemoteAddr())
|
||||
.event(Events.LOGIN).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirectUri)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "social");
|
||||
|
||||
SocialProvider provider = SocialLoader.load(providerId);
|
||||
if (provider == null) {
|
||||
audit.error(Errors.SOCIAL_PROVIDER_NOT_FOUND);
|
||||
return Flows.forms(realm, request, uriInfo).setError("Social provider not found").createErrorPage();
|
||||
}
|
||||
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
logger.warn("Unknown login requester: " + clientId);
|
||||
return Flows.forms(realm, request, uriInfo).setError("Unknown login requester.").createErrorPage();
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
audit.error(Errors.CLIENT_DISABLED);
|
||||
logger.warn("Login requester not enabled.");
|
||||
return Flows.forms(realm, request, uriInfo).setError("Login requester not enabled.").createErrorPage();
|
||||
}
|
||||
redirectUri = TokenService.verifyRedirectUri(redirectUri, client);
|
||||
if (redirectUri == null) {
|
||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forms(realm, request, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
|
||||
}
|
||||
|
||||
|
@ -248,6 +297,7 @@ public class SocialResource {
|
|||
.putClientAttribute(OAuth2Constants.STATE, state).putClientAttribute("redirectUri", redirectUri)
|
||||
.putClientAttribute("responseType", responseType).redirectToSocialProvider();
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to redirect to social auth", t);
|
||||
return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").createErrorPage();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ import org.jboss.resteasy.spi.HttpRequest;
|
|||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Errors;
|
||||
import org.keycloak.audit.Events;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -73,6 +77,7 @@ public class TokenService {
|
|||
|
||||
protected RealmModel realm;
|
||||
protected TokenManager tokenManager;
|
||||
private Audit audit;
|
||||
protected AuthenticationManager authManager = new AuthenticationManager();
|
||||
|
||||
@Context
|
||||
|
@ -97,9 +102,10 @@ public class TokenService {
|
|||
|
||||
private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
|
||||
|
||||
public TokenService(RealmModel realm, TokenManager tokenManager) {
|
||||
public TokenService(RealmModel realm, TokenManager tokenManager, Audit audit) {
|
||||
this.realm = realm;
|
||||
this.tokenManager = tokenManager;
|
||||
this.audit = audit;
|
||||
}
|
||||
|
||||
public static UriBuilder tokenServiceBaseUrl(UriInfo uriInfo) {
|
||||
|
@ -143,31 +149,42 @@ public class TokenService {
|
|||
throw new NotAcceptableException("HTTPS required");
|
||||
}
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, form);
|
||||
audit.event(Events.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, form, audit);
|
||||
|
||||
if (client.isPublicClient()) {
|
||||
// we don't allow public clients to invoke grants/access to prevent phishing attacks
|
||||
audit.error(Errors.NOT_ALLOWED);
|
||||
throw new ForbiddenException("Public clients are not allowed to invoke grants/access");
|
||||
}
|
||||
|
||||
|
||||
if (form.getFirst(AuthenticationManager.FORM_USERNAME) == null) {
|
||||
String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
if (username == null) {
|
||||
audit.error(Errors.USERNAME_MISSING);
|
||||
throw new NotAuthorizedException("No username");
|
||||
}
|
||||
audit.detail(Details.USERNAME, username);
|
||||
if (!realm.isEnabled()) {
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
throw new NotAuthorizedException("Disabled realm");
|
||||
}
|
||||
|
||||
if (authManager.authenticateForm(realm, form) != AuthenticationStatus.SUCCESS) {
|
||||
audit.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
throw new NotAuthorizedException("Auth failed");
|
||||
}
|
||||
|
||||
UserModel user = realm.getUser(form.getFirst(AuthenticationManager.FORM_USERNAME));
|
||||
String scope = form.getFirst(OAuth2Constants.SCOPE);
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
.generateAccessToken(scope, client, user)
|
||||
.generateIDToken()
|
||||
.build();
|
||||
|
||||
audit.success();
|
||||
|
||||
return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
|
||||
|
@ -182,22 +199,28 @@ public class TokenService {
|
|||
throw new NotAcceptableException("HTTPS required");
|
||||
}
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, form);
|
||||
audit.event(Events.REFRESH_TOKEN);
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, form, audit);
|
||||
String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
|
||||
AccessToken accessToken = null;
|
||||
try {
|
||||
accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken);
|
||||
accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken, audit);
|
||||
} catch (OAuthErrorException e) {
|
||||
Map<String, String> error = new HashMap<String, String>();
|
||||
error.put(OAuth2Constants.ERROR, e.getError());
|
||||
if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
|
||||
audit.error(Errors.INVALID_TOKEN);
|
||||
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(), e);
|
||||
}
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
.accessToken(accessToken)
|
||||
.generateIDToken()
|
||||
.generateRefreshToken().build();
|
||||
|
||||
audit.success();
|
||||
|
||||
return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
|
||||
}
|
||||
|
||||
|
@ -208,6 +231,23 @@ public class TokenService {
|
|||
@QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
logger.debug("TokenService.processLogin");
|
||||
|
||||
String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
|
||||
String rememberMe = formData.getFirst("rememberMe");
|
||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
||||
logger.debug("*** Remember me: " + remember);
|
||||
|
||||
audit.event(Events.LOGIN).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.USERNAME, username);
|
||||
|
||||
if (remember) {
|
||||
audit.detail(Details.REMEMBER_ME, "true");
|
||||
}
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!checkSsl()) {
|
||||
|
@ -215,30 +255,32 @@ public class TokenService {
|
|||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Realm not enabled.");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
return oauth.forwardToSecurityFailure("Unknown login requester.");
|
||||
}
|
||||
if (!client.isEnabled()) {
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||
}
|
||||
|
||||
redirect = verifyRedirectUri(redirect, client);
|
||||
if (redirect == null) {
|
||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
if (formData.containsKey("cancel")) {
|
||||
audit.error(Errors.REJECTED_BY_USER);
|
||||
return oauth.redirectError(client, "access_denied", state, redirect);
|
||||
}
|
||||
|
||||
AuthenticationStatus status = authManager.authenticateForm(realm, formData);
|
||||
|
||||
String rememberMe = formData.getFirst("rememberMe");
|
||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
||||
logger.debug("*** Remember me: " + remember);
|
||||
if (remember) {
|
||||
NewCookie cookie = authManager.createRememberMeCookie(realm, uriInfo);
|
||||
response.addNewCookie(cookie);
|
||||
|
@ -249,20 +291,26 @@ public class TokenService {
|
|||
switch (status) {
|
||||
case SUCCESS:
|
||||
case ACTIONS_REQUIRED:
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, formData.getFirst(AuthenticationManager.FORM_USERNAME));
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||
audit.user(user);
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, username, remember, "form", audit);
|
||||
case ACCOUNT_DISABLED:
|
||||
audit.error(Errors.USER_DISABLED);
|
||||
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
|
||||
case MISSING_TOTP:
|
||||
return Flows.forms(realm, request, uriInfo).setFormData(formData).createLoginTotp();
|
||||
case INVALID_USER:
|
||||
audit.error(Errors.USER_NOT_FOUND);
|
||||
return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
|
||||
default:
|
||||
audit.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
|
||||
}
|
||||
}
|
||||
|
||||
@Path("auth/request/login-actions")
|
||||
public RequiredActionsService getRequiredActionsService() {
|
||||
RequiredActionsService service = new RequiredActionsService(realm, tokenManager);
|
||||
RequiredActionsService service = new RequiredActionsService(realm, tokenManager, audit);
|
||||
resourceContext.initResource(service);
|
||||
return service;
|
||||
}
|
||||
|
@ -273,30 +321,46 @@ public class TokenService {
|
|||
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) {
|
||||
|
||||
String username = formData.getFirst("username");
|
||||
String email = formData.getFirst("email");
|
||||
|
||||
audit.event(Events.REGISTER).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.USERNAME, username)
|
||||
.detail(Details.EMAIL, email)
|
||||
.detail(Details.REGISTER_METHOD, "form");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
logger.warn("Realm not enabled");
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Realm not enabled");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
logger.warn("Unknown login requester.");
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
return oauth.forwardToSecurityFailure("Unknown login requester.");
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
logger.warn("Login requester not enabled.");
|
||||
audit.error(Errors.CLIENT_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||
}
|
||||
|
||||
redirect = verifyRedirectUri(redirect, client);
|
||||
if (redirect == null) {
|
||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
logger.warn("Registration not allowed");
|
||||
audit.error(Errors.REGISTRATION_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Registration not allowed");
|
||||
}
|
||||
|
||||
|
@ -312,13 +376,13 @@ public class TokenService {
|
|||
}
|
||||
|
||||
if (error != null) {
|
||||
audit.error(Errors.INVALID_REGISTRATION);
|
||||
return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData).createRegistration();
|
||||
}
|
||||
|
||||
String username = formData.getFirst("username");
|
||||
|
||||
UserModel user = realm.getUser(username);
|
||||
if (user != null) {
|
||||
audit.error(Errors.USERNAME_IN_USE);
|
||||
return Flows.forms(realm, request, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration();
|
||||
}
|
||||
|
||||
|
@ -327,7 +391,7 @@ public class TokenService {
|
|||
user.setFirstName(formData.getFirst("firstName"));
|
||||
user.setLastName(formData.getFirst("lastName"));
|
||||
|
||||
user.setEmail(formData.getFirst("email"));
|
||||
user.setEmail(email);
|
||||
|
||||
if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
|
||||
UserCredentialModel credentials = new UserCredentialModel();
|
||||
|
@ -342,6 +406,9 @@ public class TokenService {
|
|||
}
|
||||
}
|
||||
|
||||
audit.user(user).success();
|
||||
audit.reset();
|
||||
|
||||
return processLogin(clientId, scopeParam, state, redirect, formData);
|
||||
}
|
||||
|
||||
|
@ -362,19 +429,20 @@ public class TokenService {
|
|||
throw new NotAcceptableException("HTTPS required");
|
||||
}
|
||||
|
||||
audit.event(Events.CODE_TO_TOKEN);
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
throw new NotAuthorizedException("Realm not enabled");
|
||||
}
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, formData);
|
||||
|
||||
String code = formData.getFirst(OAuth2Constants.CODE);
|
||||
if (code == null) {
|
||||
Map<String, String> error = new HashMap<String, String>();
|
||||
error.put(OAuth2Constants.ERROR, "invalid_request");
|
||||
error.put(OAuth2Constants.ERROR_DESCRIPTION, "code not specified");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
throw new BadRequestException("Code not specified", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
||||
|
||||
}
|
||||
|
||||
JWSInput input = new JWSInput(code);
|
||||
|
@ -388,22 +456,33 @@ public class TokenService {
|
|||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Unable to verify code signature");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
String key = input.readContentAsString();
|
||||
|
||||
audit.detail(Details.CODE_ID, key);
|
||||
|
||||
AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
|
||||
if (accessCode == null) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code not found");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
||||
audit.user(accessCode.getUser());
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, formData, audit);
|
||||
|
||||
if (accessCode.isExpired()) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code is expired");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
@ -411,6 +490,7 @@ public class TokenService {
|
|||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
@ -418,19 +498,24 @@ public class TokenService {
|
|||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Auth error");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
||||
logger.debug("accessRequest SUCCESS");
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
.accessToken(accessCode.getToken())
|
||||
.generateIDToken()
|
||||
.generateRefreshToken().build();
|
||||
|
||||
audit.success();
|
||||
|
||||
return Cors.add(request, Response.ok(res)).auth().allowedOrigins(client).allowedMethods("POST").build();
|
||||
}
|
||||
|
||||
protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData) {
|
||||
protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, Audit audit) {
|
||||
String client_id = null;
|
||||
String clientSecret = null;
|
||||
if (authorizationHeader != null) {
|
||||
|
@ -453,11 +538,14 @@ public class TokenService {
|
|||
throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
||||
}
|
||||
|
||||
audit.client(client_id);
|
||||
|
||||
ClientModel client = realm.findClient(client_id);
|
||||
if (client == null) {
|
||||
Map<String, String> error = new HashMap<String, String>();
|
||||
error.put(OAuth2Constants.ERROR, "invalid_client");
|
||||
error.put(OAuth2Constants.ERROR_DESCRIPTION, "Could not find client");
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
||||
}
|
||||
|
||||
|
@ -465,6 +553,7 @@ public class TokenService {
|
|||
Map<String, String> error = new HashMap<String, String>();
|
||||
error.put(OAuth2Constants.ERROR, "invalid_client");
|
||||
error.put(OAuth2Constants.ERROR_DESCRIPTION, "Client is not enabled");
|
||||
audit.error(Errors.CLIENT_DISABLED);
|
||||
throw new BadRequestException("Client is not enabled", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
||||
}
|
||||
|
||||
|
@ -472,6 +561,7 @@ public class TokenService {
|
|||
if (!client.validateSecret(clientSecret)) {
|
||||
Map<String, String> error = new HashMap<String, String>();
|
||||
error.put(OAuth2Constants.ERROR, "unauthorized_client");
|
||||
audit.error(Errors.INVALID_CLIENT_CREDENTIALS);
|
||||
throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
|
||||
}
|
||||
}
|
||||
|
@ -484,6 +574,9 @@ public class TokenService {
|
|||
@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) {
|
||||
logger.info("TokenService.loginPage");
|
||||
|
||||
audit.event(Events.LOGIN).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!checkSsl()) {
|
||||
|
@ -492,20 +585,24 @@ public class TokenService {
|
|||
|
||||
if (!realm.isEnabled()) {
|
||||
logger.warn("Realm not enabled");
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Realm not enabled");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
logger.warn("Unknown login requester: " + clientId);
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
return oauth.forwardToSecurityFailure("Unknown login requester.");
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
logger.warn("Login requester not enabled.");
|
||||
audit.error(Errors.CLIENT_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||
}
|
||||
redirect = verifyRedirectUri(redirect, client);
|
||||
if (redirect == null) {
|
||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
|
@ -513,7 +610,8 @@ public class TokenService {
|
|||
UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
|
||||
if (user != null) {
|
||||
logger.debug(user.getLoginName() + " already logged in.");
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user);
|
||||
audit.user(user).detail(Details.AUTH_METHOD, "sso");
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, null, false, "sso", audit);
|
||||
}
|
||||
|
||||
if (prompt != null && prompt.equals("none")) {
|
||||
|
@ -529,6 +627,9 @@ public class TokenService {
|
|||
@QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
|
||||
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
|
||||
logger.info("**********registerPage()");
|
||||
|
||||
audit.event(Events.REGISTER).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!checkSsl()) {
|
||||
|
@ -537,26 +638,31 @@ public class TokenService {
|
|||
|
||||
if (!realm.isEnabled()) {
|
||||
logger.warn("Realm not enabled");
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Realm not enabled");
|
||||
}
|
||||
ClientModel client = realm.findClient(clientId);
|
||||
if (client == null) {
|
||||
logger.warn("Unknown login requester.");
|
||||
audit.error(Errors.CLIENT_NOT_FOUND);
|
||||
return oauth.forwardToSecurityFailure("Unknown login requester.");
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
logger.warn("Login requester not enabled.");
|
||||
audit.error(Errors.CLIENT_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||
}
|
||||
|
||||
redirect = verifyRedirectUri(redirect, client);
|
||||
if (redirect == null) {
|
||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
logger.warn("Registration not allowed");
|
||||
audit.error(Errors.REGISTRATION_DISABLED);
|
||||
return oauth.forwardToSecurityFailure("Registration not allowed");
|
||||
}
|
||||
|
||||
|
@ -571,6 +677,8 @@ public class TokenService {
|
|||
public Response logout(final @QueryParam("redirect_uri") String redirectUri) {
|
||||
// todo do we care if anybody can trigger this?
|
||||
|
||||
audit.event(Events.LOGOUT).detail(Details.REDIRECT_URI, redirectUri);
|
||||
|
||||
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
||||
UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers, false);
|
||||
if (user != null) {
|
||||
|
@ -578,6 +686,8 @@ public class TokenService {
|
|||
authManager.expireIdentityCookie(realm, uriInfo);
|
||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
||||
resourceAdminManager.logoutUser(realm, user);
|
||||
|
||||
audit.user(user).success();
|
||||
} else {
|
||||
logger.info("No user logged in for logout");
|
||||
}
|
||||
|
@ -589,6 +699,8 @@ public class TokenService {
|
|||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response processOAuth(final MultivaluedMap<String, String> formData) {
|
||||
audit.event(Events.LOGIN).detail(Details.RESPONSE_TYPE, "code");
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!checkSsl()) {
|
||||
|
@ -604,21 +716,39 @@ public class TokenService {
|
|||
logger.debug("Failed to verify signature", ignored);
|
||||
}
|
||||
if (!verifiedCode) {
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return oauth.forwardToSecurityFailure("Illegal access code.");
|
||||
}
|
||||
String key = input.readContentAsString();
|
||||
audit.detail(Details.CODE_ID, key);
|
||||
|
||||
AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
|
||||
if (accessCodeEntry == null) {
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return oauth.forwardToSecurityFailure("Unknown access code.");
|
||||
}
|
||||
|
||||
String redirect = accessCodeEntry.getRedirectUri();
|
||||
String state = accessCodeEntry.getState();
|
||||
|
||||
audit.client(accessCodeEntry.getClient())
|
||||
.user(accessCodeEntry.getUser())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, accessCodeEntry.getAuthMethod())
|
||||
.detail(Details.REDIRECT_URI, redirect)
|
||||
.detail(Details.USERNAME, accessCodeEntry.getUsername());
|
||||
|
||||
if (accessCodeEntry.isRememberMe()) {
|
||||
audit.detail(Details.REMEMBER_ME, "true");
|
||||
}
|
||||
|
||||
if (formData.containsKey("cancel")) {
|
||||
audit.error(Errors.REJECTED_BY_USER);
|
||||
return redirectAccessDenied(redirect, state);
|
||||
}
|
||||
|
||||
audit.success();
|
||||
|
||||
accessCodeEntry.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
|
||||
}
|
||||
|
|
|
@ -24,23 +24,22 @@ package org.keycloak.services.resources.flows;
|
|||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Events;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.OAuthClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.AccessCodeEntry;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.resources.TokenService;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
@ -56,15 +55,15 @@ public class OAuthFlows {
|
|||
|
||||
private static final Logger log = Logger.getLogger(OAuthFlows.class);
|
||||
|
||||
private RealmModel realm;
|
||||
private final RealmModel realm;
|
||||
|
||||
private HttpRequest request;
|
||||
private final HttpRequest request;
|
||||
|
||||
private UriInfo uriInfo;
|
||||
private final UriInfo uriInfo;
|
||||
|
||||
private AuthenticationManager authManager;
|
||||
private final AuthenticationManager authManager;
|
||||
|
||||
private TokenManager tokenManager;
|
||||
private final TokenManager tokenManager;
|
||||
|
||||
OAuthFlows(RealmModel realm, HttpRequest request, UriInfo uriInfo, AuthenticationManager authManager,
|
||||
TokenManager tokenManager) {
|
||||
|
@ -110,28 +109,40 @@ public class OAuthFlows {
|
|||
}
|
||||
}
|
||||
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user) {
|
||||
return processAccessCode(scopeParam, state, redirect, client, user, false);
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, Audit audit) {
|
||||
return processAccessCode(scopeParam, state, redirect, client, user, null, false, "form", audit);
|
||||
}
|
||||
|
||||
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, boolean rememberMe) {
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, String username, boolean rememberMe, String authMethod, Audit audit) {
|
||||
isTotpConfigurationRequired(user);
|
||||
isEmailVerificationRequired(user);
|
||||
|
||||
boolean isResource = client instanceof ApplicationModel;
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
|
||||
accessCode.setUsername(username);
|
||||
accessCode.setRememberMe(rememberMe);
|
||||
accessCode.setAuthMethod(authMethod);
|
||||
|
||||
log.debug("processAccessCode: isResource: {0}", isResource);
|
||||
log.debug("processAccessCode: go to oauth page?: {0}",
|
||||
(!isResource && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested()
|
||||
.size() > 0)));
|
||||
|
||||
audit.detail(Details.CODE_ID, accessCode.getId());
|
||||
|
||||
Set<RequiredAction> requiredActions = user.getRequiredActions();
|
||||
if (!requiredActions.isEmpty()) {
|
||||
accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
|
||||
RequiredAction action = user.getRequiredActions().iterator().next();
|
||||
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
|
||||
audit.clone().event(Events.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
|
||||
}
|
||||
|
||||
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
|
||||
.createResponse(user.getRequiredActions().iterator().next());
|
||||
.createResponse(action);
|
||||
}
|
||||
|
||||
if (!isResource
|
||||
|
@ -143,6 +154,7 @@ public class OAuthFlows {
|
|||
}
|
||||
|
||||
if (redirect != null) {
|
||||
audit.success();
|
||||
return redirectAccessCode(accessCode, state, redirect, rememberMe);
|
||||
} else {
|
||||
return null;
|
||||
|
|
|
@ -36,6 +36,16 @@
|
|||
<artifactId>keycloak-admin-ui</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-jboss-logging</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-admin-ui-styles</artifactId>
|
||||
|
|
|
@ -21,18 +21,12 @@
|
|||
*/
|
||||
package org.keycloak.testsuite;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
package org.keycloak.testsuite;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.junit.Assert;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AssertEvents implements TestRule, AuditListener{
|
||||
|
||||
private static final Logger log = Logger.getLogger(AssertEvents.class);
|
||||
|
||||
public static String DEFAULT_CLIENT_ID = "test-app";
|
||||
public static String DEFAULT_REDIRECT_URI = "http://localhost:8081/app/auth";
|
||||
public static String DEFAULT_IP_ADDRESS = "127.0.0.1";
|
||||
public static String DEFAULT_REALM = "test";
|
||||
public static String DEFAULT_USERNAME = "test-user@localhost";
|
||||
|
||||
private KeycloakRule keycloak;
|
||||
|
||||
private static BlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
|
||||
|
||||
public AssertEvents() {
|
||||
}
|
||||
|
||||
public AssertEvents(KeycloakRule keycloak) {
|
||||
this.keycloak = keycloak;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "assert-events";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement base, org.junit.runner.Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
events.clear();
|
||||
|
||||
keycloak.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
Set<String> listeners = new HashSet<String>();
|
||||
listeners.add("jboss-logging");
|
||||
listeners.add("assert-events");
|
||||
appRealm.setAuditListeners(listeners);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
base.evaluate();
|
||||
|
||||
Event event = events.peek();
|
||||
if (event != null) {
|
||||
Assert.fail("Unexpected event after test: " + event.getEvent());
|
||||
}
|
||||
} finally {
|
||||
keycloak.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
appRealm.setAuditListeners(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void assertEmpty() {
|
||||
Assert.assertTrue(events.isEmpty());
|
||||
}
|
||||
|
||||
public Event poll() {
|
||||
try {
|
||||
return events.poll(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
public ExpectedEvent expectRequiredAction(String event) {
|
||||
return expectLogin().event(event);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectLogin() {
|
||||
return expect("login")
|
||||
.detail(Details.CODE_ID, isCodeId())
|
||||
.detail(Details.USERNAME, DEFAULT_USERNAME)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectCodeToToken(String codeId) {
|
||||
return expect("code_to_token")
|
||||
.detail(Details.CODE_ID, codeId)
|
||||
.detail(Details.TOKEN_ID, isUUID())
|
||||
.detail(Details.REFRESH_TOKEN_ID, isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectRefresh(String refreshTokenId) {
|
||||
return expect("refresh_token")
|
||||
.detail(Details.TOKEN_ID, isUUID())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
|
||||
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectLogout() {
|
||||
return expect("logout").client((String) null)
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectRegister(String username, String email) {
|
||||
UserRepresentation user = keycloak.getUser("test", username);
|
||||
return expect("register")
|
||||
.user(user != null ? user.getId() : null)
|
||||
.detail(Details.USERNAME, username)
|
||||
.detail(Details.EMAIL, email)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REGISTER_METHOD, "form")
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectAccount(String event) {
|
||||
return expect(event).client("account");
|
||||
}
|
||||
|
||||
public ExpectedEvent expect(String event) {
|
||||
return new ExpectedEvent().realm(DEFAULT_REALM).client(DEFAULT_CLIENT_ID).user(keycloak.getUser(DEFAULT_REALM, DEFAULT_USERNAME).getId()).ipAddress(DEFAULT_IP_ADDRESS).event(event);
|
||||
}
|
||||
|
||||
public static class ExpectedEvent {
|
||||
private Event expected = new Event();
|
||||
private Matcher<String> userId;
|
||||
private HashMap<String, Matcher<String>> details;
|
||||
|
||||
public ExpectedEvent realm(RealmModel realm) {
|
||||
expected.setRealmId(realm.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent realm(String realmId) {
|
||||
expected.setRealmId(realmId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent client(ClientModel client) {
|
||||
expected.setClientId(client.getClientId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent client(String clientId) {
|
||||
expected.setClientId(clientId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent user(UserModel user) {
|
||||
return user(CoreMatchers.equalTo(user.getId()));
|
||||
}
|
||||
|
||||
public ExpectedEvent user(String userId) {
|
||||
return user(CoreMatchers.equalTo(userId));
|
||||
}
|
||||
|
||||
public ExpectedEvent user(Matcher<String> userId) {
|
||||
this.userId = userId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent ipAddress(String ipAddress) {
|
||||
expected.setIpAddress(ipAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent event(String e) {
|
||||
expected.setEvent(e);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent detail(String key, String value) {
|
||||
return detail(key, CoreMatchers.equalTo(value));
|
||||
}
|
||||
|
||||
public ExpectedEvent detail(String key, Matcher<String> matcher) {
|
||||
if (details == null) {
|
||||
details = new HashMap<String, Matcher<String>>();
|
||||
}
|
||||
details.put(key, matcher);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent removeDetail(String key) {
|
||||
if (details != null) {
|
||||
details.remove(key);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent error(String error) {
|
||||
expected.setError(error);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Event assertEvent() {
|
||||
try {
|
||||
return assertEvent(events.poll(10, TimeUnit.SECONDS));
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError("No event received within timeout");
|
||||
}
|
||||
}
|
||||
|
||||
public Event assertEvent(Event actual) {
|
||||
Assert.assertEquals(expected.getEvent(), actual.getEvent());
|
||||
Assert.assertEquals(expected.getRealmId(), actual.getRealmId());
|
||||
Assert.assertEquals(expected.getClientId(), actual.getClientId());
|
||||
Assert.assertEquals(expected.getError(), actual.getError());
|
||||
Assert.assertEquals(expected.getIpAddress(), actual.getIpAddress());
|
||||
Assert.assertThat(actual.getUserId(), userId);
|
||||
|
||||
if (details == null) {
|
||||
Assert.assertNull(actual.getDetails());
|
||||
} else {
|
||||
Assert.assertNotNull(actual.getDetails());
|
||||
for (Map.Entry<String, Matcher<String>> d : details.entrySet()) {
|
||||
String actualValue = actual.getDetails().get(d.getKey());
|
||||
if (!actual.getDetails().containsKey(d.getKey())) {
|
||||
Assert.fail(d.getKey() + " missing");
|
||||
}
|
||||
|
||||
if (!d.getValue().matches(actualValue)) {
|
||||
Assert.fail(d.getKey() + " doesn't match");
|
||||
}
|
||||
}
|
||||
|
||||
for (String k : actual.getDetails().keySet()) {
|
||||
if (!details.containsKey(k)) {
|
||||
Assert.fail(k + " was not expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actual;
|
||||
}
|
||||
}
|
||||
|
||||
public static Matcher<String> isCodeId() {
|
||||
return new TypeSafeMatcher<String>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(String item) {
|
||||
return (UUID.randomUUID().toString() + System.currentTimeMillis()).length() == item.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Not an Code ID");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<String> isUUID() {
|
||||
return new TypeSafeMatcher<String>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(String item) {
|
||||
return KeycloakModelUtils.generateId().length() == item.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Not an UUID");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -11,7 +11,6 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
|
@ -19,24 +19,38 @@
|
|||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
*/
|
||||
package org.keycloak.testsuite.forms;
|
||||
package org.keycloak.testsuite.account;
|
||||
|
||||
import org.junit.*;
|
||||
import org.keycloak.models.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.*;
|
||||
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||
import org.keycloak.testsuite.pages.AccountTotpPage;
|
||||
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -62,6 +76,11 @@ public class AccountTest {
|
|||
}
|
||||
});
|
||||
|
||||
public static String ACCOUNT_REDIRECT = "http://localhost:8081/auth/rest/realms/test/account/login-redirect";
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -90,6 +109,12 @@ public class AccountTest {
|
|||
protected ErrorPage errorPage;
|
||||
|
||||
private TimeBasedOTP totp = new TimeBasedOTP();
|
||||
private String userId;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
userId = keycloakRule.getUser("test", "test-user@localhost").getId();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
|
@ -122,6 +147,8 @@ public class AccountTest {
|
|||
|
||||
Assert.assertTrue(appPage.isCurrent());
|
||||
Assert.assertEquals(appPage.baseUrl + "?test", driver.getCurrentUrl());
|
||||
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -129,6 +156,8 @@ public class AccountTest {
|
|||
changePasswordPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("", "new-password", "new-password");
|
||||
|
||||
Assert.assertEquals("Please specify password.", profilePage.getError());
|
||||
|
@ -141,6 +170,8 @@ public class AccountTest {
|
|||
|
||||
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount("update_password").assertEvent();
|
||||
|
||||
changePasswordPage.logout();
|
||||
|
||||
loginPage.open();
|
||||
|
@ -148,10 +179,14 @@ public class AccountTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "new-password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -167,6 +202,8 @@ public class AccountTest {
|
|||
changePasswordPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
|
||||
|
||||
changePasswordPage.changePassword("", "new", "new");
|
||||
|
||||
Assert.assertEquals("Please specify password.", profilePage.getError());
|
||||
|
@ -174,6 +211,8 @@ public class AccountTest {
|
|||
changePasswordPage.changePassword("password", "new-password", "new-password");
|
||||
|
||||
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount("update_password").assertEvent();
|
||||
} finally {
|
||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
|
@ -189,6 +228,8 @@ public class AccountTest {
|
|||
profilePage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
|
||||
|
||||
Assert.assertEquals("", profilePage.getFirstName());
|
||||
Assert.assertEquals("", profilePage.getLastName());
|
||||
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||
|
@ -201,6 +242,8 @@ public class AccountTest {
|
|||
Assert.assertEquals("", profilePage.getLastName());
|
||||
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||
|
||||
events.assertEmpty();
|
||||
|
||||
profilePage.updateProfile("New first", "", "new@email.com");
|
||||
|
||||
Assert.assertEquals("Please specify last name", profilePage.getError());
|
||||
|
@ -208,6 +251,8 @@ public class AccountTest {
|
|||
Assert.assertEquals("", profilePage.getLastName());
|
||||
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||
|
||||
events.assertEmpty();
|
||||
|
||||
profilePage.updateProfile("New first", "New last", "");
|
||||
|
||||
Assert.assertEquals("Please specify email", profilePage.getError());
|
||||
|
@ -215,12 +260,17 @@ public class AccountTest {
|
|||
Assert.assertEquals("", profilePage.getLastName());
|
||||
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||
|
||||
events.assertEmpty();
|
||||
|
||||
profilePage.updateProfile("New first", "New last", "new@email.com");
|
||||
|
||||
Assert.assertEquals("Your account has been updated", profilePage.getSuccess());
|
||||
Assert.assertEquals("New first", profilePage.getFirstName());
|
||||
Assert.assertEquals("New last", profilePage.getLastName());
|
||||
Assert.assertEquals("new@email.com", profilePage.getEmail());
|
||||
|
||||
events.expectAccount("update_profile").assertEvent();
|
||||
events.expectAccount("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -228,6 +278,8 @@ public class AccountTest {
|
|||
totpPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=totp").assertEvent();
|
||||
|
||||
Assert.assertTrue(totpPage.isCurrent());
|
||||
|
||||
Assert.assertFalse(driver.getPageSource().contains("Remove Google"));
|
||||
|
@ -241,7 +293,13 @@ public class AccountTest {
|
|||
|
||||
Assert.assertEquals("Google authenticator configured.", profilePage.getSuccess());
|
||||
|
||||
events.expectAccount("update_totp").assertEvent();
|
||||
|
||||
Assert.assertTrue(driver.getPageSource().contains("pficon-delete"));
|
||||
|
||||
totpPage.removeTotp();
|
||||
|
||||
events.expectAccount("remove_totp").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -249,6 +307,10 @@ public class AccountTest {
|
|||
profilePage.open();
|
||||
loginPage.login("test-user-no-access@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").user(keycloakRule.getUser("test", "test-user-no-access@localhost").getId())
|
||||
.detail(Details.USERNAME, "test-user-no-access@localhost")
|
||||
.detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
|
||||
|
||||
Assert.assertTrue(errorPage.isCurrent());
|
||||
Assert.assertEquals("No access", errorPage.getError());
|
||||
}
|
|
@ -36,7 +36,6 @@ import java.io.IOException;
|
|||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,13 +22,17 @@
|
|||
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;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -53,17 +57,10 @@ import java.util.regex.Pattern;
|
|||
public class RequiredActionEmailVerificationTest {
|
||||
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
||||
appRealm.setVerifyEmail(true);
|
||||
|
||||
UserModel user = appRealm.getUser("test-user@localhost");
|
||||
user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
|
||||
}
|
||||
|
||||
});
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
@ -74,6 +71,9 @@ public class RequiredActionEmailVerificationTest {
|
|||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected AppPage appPage;
|
||||
|
||||
|
@ -86,6 +86,21 @@ public class RequiredActionEmailVerificationTest {
|
|||
@WebResource
|
||||
protected RegisterPage registerPage;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
keycloakRule.configure(new KeycloakSetup() {
|
||||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
||||
appRealm.setVerifyEmail(true);
|
||||
|
||||
UserModel user = appRealm.getUser("test-user@localhost");
|
||||
user.setEmailVerified(false);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyEmailExisting() throws IOException, MessagingException {
|
||||
loginPage.open();
|
||||
|
@ -105,9 +120,19 @@ public class RequiredActionEmailVerificationTest {
|
|||
|
||||
String verificationUrl = m.group(1);
|
||||
|
||||
Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1]);
|
||||
|
||||
driver.navigate().to(verificationUrl.trim());
|
||||
|
||||
events.expectRequiredAction("verify_email").detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -116,6 +141,8 @@ public class RequiredActionEmailVerificationTest {
|
|||
loginPage.clickRegister();
|
||||
registerPage.register("firstName", "lastName", "email", "verifyEmail", "password", "password");
|
||||
|
||||
String userId = events.expectRegister("verifyEmail", "email").assertEvent().getUserId();
|
||||
|
||||
Assert.assertTrue(verifyEmailPage.isCurrent());
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
@ -128,23 +155,34 @@ public class RequiredActionEmailVerificationTest {
|
|||
Matcher m = p.matcher(body);
|
||||
m.matches();
|
||||
|
||||
Event sendEvent = events.expectRequiredAction("send_verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").assertEvent();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String verificationUrl = m.group(1);
|
||||
|
||||
driver.navigate().to(verificationUrl.trim());
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectRequiredAction("verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
|
||||
events.expectLogin().user(userId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyEmailResend() throws IOException, MessagingException {
|
||||
loginPage.open();
|
||||
loginPage.clickRegister();
|
||||
registerPage.register("firstName2", "lastName2", "email2", "verifyEmail2", "password2", "password2");
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
Assert.assertTrue(verifyEmailPage.isCurrent());
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
verifyEmailPage.clickResendEmail();
|
||||
|
||||
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
||||
|
@ -157,11 +195,17 @@ public class RequiredActionEmailVerificationTest {
|
|||
Matcher m = p.matcher(body);
|
||||
m.matches();
|
||||
|
||||
events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent(sendEvent);
|
||||
|
||||
String verificationUrl = m.group(1);
|
||||
|
||||
driver.navigate().to(verificationUrl.trim());
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectRequiredAction("verify_email").detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,13 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -60,9 +63,15 @@ public class RequiredActionMultipleActionsTest {
|
|||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected AppPage appPage;
|
||||
|
||||
|
@ -95,14 +104,21 @@ public class RequiredActionMultipleActionsTest {
|
|||
}
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
public void updatePassword() {
|
||||
changePasswordPage.changePassword("new-password", "new-password");
|
||||
|
||||
events.expectRequiredAction("update_password").assertEvent();
|
||||
}
|
||||
|
||||
public void updateProfile() {
|
||||
updateProfilePage.update("New first", "New last", "new@email.com");
|
||||
|
||||
events.expectRequiredAction("update_profile").assertEvent();
|
||||
events.expectRequiredAction("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
|
@ -62,6 +63,9 @@ public class RequiredActionResetPasswordTest {
|
|||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public GreenMailRule greenMail = new GreenMailRule();
|
||||
|
||||
|
@ -88,12 +92,20 @@ public class RequiredActionResetPasswordTest {
|
|||
changePasswordPage.assertCurrent();
|
||||
changePasswordPage.changePassword("new-password", "new-password");
|
||||
|
||||
events.expectRequiredAction("update_password").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "new-password");
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,12 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AccountTotpPage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
|
@ -59,6 +61,9 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
});
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -94,11 +99,17 @@ public class RequiredActionTotpSetupTest {
|
|||
loginPage.clickRegister();
|
||||
registerPage.register("firstName", "lastName", "email", "setupTotp", "password", "password");
|
||||
|
||||
String userId = events.expectRegister("setupTotp", "email").assertEvent().getUserId();
|
||||
|
||||
totpPage.assertCurrent();
|
||||
|
||||
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||
|
||||
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,15 +123,23 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
totpPage.configure(totp.generate(totpSecret));
|
||||
|
||||
events.expectRequiredAction("update_totp").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
loginTotpPage.login(totp.generate(totpSecret));
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -130,6 +149,8 @@ public class RequiredActionTotpSetupTest {
|
|||
loginPage.clickRegister();
|
||||
registerPage.register("firstName2", "lastName2", "email2", "setupTotp2", "password2", "password2");
|
||||
|
||||
String userId = events.expectRegister("setupTotp2", "email2").assertEvent().getUserId();
|
||||
|
||||
// Configure totp
|
||||
totpPage.assertCurrent();
|
||||
|
||||
|
@ -139,8 +160,13 @@ public class RequiredActionTotpSetupTest {
|
|||
// After totp config, user should be on the app page
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
// Logout
|
||||
oauth.openLogout();
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
|
||||
// Try to login after logout
|
||||
loginPage.open();
|
||||
|
@ -153,15 +179,24 @@ public class RequiredActionTotpSetupTest {
|
|||
// Login with one-time password
|
||||
loginTotpPage.login(totp.generate(totpCode));
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
// Open account page
|
||||
accountTotpPage.open();
|
||||
accountTotpPage.assertCurrent();
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.AUTH_METHOD, "sso").client("account")
|
||||
.detail(Details.REDIRECT_URI, "http://localhost:8081/auth/rest/realms/test/account/login-redirect?path=totp")
|
||||
.removeDetail(Details.USERNAME).assertEvent();
|
||||
|
||||
// Remove google authentificator
|
||||
accountTotpPage.removeTotp();
|
||||
|
||||
events.expectAccount("remove_totp").user(userId).assertEvent();
|
||||
|
||||
// Logout
|
||||
oauth.openLogout();
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
|
||||
// Try to login
|
||||
loginPage.open();
|
||||
|
@ -171,7 +206,11 @@ public class RequiredActionTotpSetupTest {
|
|||
totpPage.assertCurrent();
|
||||
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||
|
||||
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,18 +22,20 @@
|
|||
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;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
@ -43,20 +45,15 @@ import org.openqa.selenium.WebDriver;
|
|||
*/
|
||||
public class RequiredActionUpdateProfileTest {
|
||||
|
||||
@Rule
|
||||
public KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
|
||||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
||||
UserModel user = appRealm.getUser("test-user@localhost");
|
||||
user.addRequiredAction(RequiredAction.UPDATE_PROFILE);
|
||||
}
|
||||
|
||||
});
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
|
@ -69,6 +66,17 @@ public class RequiredActionUpdateProfileTest {
|
|||
@WebResource
|
||||
protected LoginUpdateProfilePage updateProfilePage;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
||||
UserModel user = appRealm.getUser("test-user@localhost");
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateProfile() {
|
||||
loginPage.open();
|
||||
|
@ -79,7 +87,12 @@ public class RequiredActionUpdateProfileTest {
|
|||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com");
|
||||
|
||||
events.expectRequiredAction("update_profile").assertEvent();
|
||||
events.expectRequiredAction("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -95,6 +108,8 @@ public class RequiredActionUpdateProfileTest {
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Please specify first name", updateProfilePage.getError());
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -110,6 +125,8 @@ public class RequiredActionUpdateProfileTest {
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Please specify last name", updateProfilePage.getError());
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -125,7 +142,8 @@ public class RequiredActionUpdateProfileTest {
|
|||
updateProfilePage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Please specify email", updateProfilePage.getError());
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
|
@ -34,6 +29,11 @@ import org.keycloak.testsuite.rule.WebResource;
|
|||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
|
|
@ -26,11 +26,13 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
|
@ -53,6 +55,8 @@ public class LoginTest {
|
|||
user.setEmail("login@test.com");
|
||||
user.setEnabled(true);
|
||||
|
||||
userId = user.getId();
|
||||
|
||||
UserCredentialModel creds = new UserCredentialModel();
|
||||
creds.setType(CredentialRepresentation.PASSWORD);
|
||||
creds.setValue("password");
|
||||
|
@ -61,6 +65,9 @@ public class LoginTest {
|
|||
}
|
||||
});
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -76,6 +83,8 @@ public class LoginTest {
|
|||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
private static String userId;
|
||||
|
||||
@Test
|
||||
public void loginInvalidPassword() {
|
||||
loginPage.open();
|
||||
|
@ -84,6 +93,8 @@ public class LoginTest {
|
|||
loginPage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -94,6 +105,8 @@ public class LoginTest {
|
|||
loginPage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").removeDetail(Details.CODE_ID).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -103,6 +116,8 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,6 +127,8 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -120,8 +137,9 @@ public class LoginTest {
|
|||
loginPage.cancel();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,12 +26,14 @@ import org.junit.Before;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -44,6 +46,7 @@ import org.keycloak.testsuite.rule.WebRule;
|
|||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -63,10 +66,14 @@ public class LoginTotpTest {
|
|||
appRealm.updateCredential(user, credentials);
|
||||
|
||||
user.setTotp(true);
|
||||
appRealm.setAuditListeners(Collections.singleton("dummy"));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -83,7 +90,7 @@ public class LoginTotpTest {
|
|||
protected LoginPage loginPage;
|
||||
|
||||
@WebResource
|
||||
private LoginTotpPage loginTotpPage;
|
||||
protected LoginTotpPage loginTotpPage;
|
||||
|
||||
private TimeBasedOTP totp = new TimeBasedOTP();
|
||||
|
||||
|
@ -103,6 +110,8 @@ public class LoginTotpTest {
|
|||
|
||||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).user((String) null).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,6 +124,8 @@ public class LoginTotpTest {
|
|||
loginTotpPage.login(totp.generate("totpSecret"));
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,9 +25,12 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -45,6 +48,9 @@ public class RegisterTest {
|
|||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -60,6 +66,9 @@ public class RegisterTest {
|
|||
@WebResource
|
||||
protected RegisterPage registerPage;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@Test
|
||||
public void registerExistingUser() {
|
||||
loginPage.open();
|
||||
|
@ -70,6 +79,8 @@ public class RegisterTest {
|
|||
|
||||
registerPage.assertCurrent();
|
||||
Assert.assertEquals("Username already exists", registerPage.getError());
|
||||
|
||||
events.expectRegister("test-user@localhost", "email").user((String) null).error("username_in_use").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,6 +93,8 @@ public class RegisterTest {
|
|||
|
||||
registerPage.assertCurrent();
|
||||
Assert.assertEquals("Password confirmation doesn't match", registerPage.getError());
|
||||
|
||||
events.expectRegister("registerUserInvalidPasswordConfirm", "email").user((String) null).error("invalid_registration").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -94,6 +107,8 @@ public class RegisterTest {
|
|||
|
||||
registerPage.assertCurrent();
|
||||
Assert.assertEquals("Please specify password.", registerPage.getError());
|
||||
|
||||
events.expectRegister("registerUserMissingPassword", "email").user((String) null).error("invalid_registration").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,8 +130,14 @@ public class RegisterTest {
|
|||
registerPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid password: minimum length 8", registerPage.getError());
|
||||
|
||||
events.expectRegister("registerPasswordPolicy", "email").user((String) null).error("invalid_registration").assertEvent();
|
||||
|
||||
registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "password", "password");
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
String userId = events.expectRegister("registerPasswordPolicy", "email").assertEvent().getUserId();
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "registerPasswordPolicy").assertEvent();
|
||||
} finally {
|
||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
|
@ -137,6 +158,8 @@ public class RegisterTest {
|
|||
|
||||
registerPage.assertCurrent();
|
||||
Assert.assertEquals("Please specify username", registerPage.getError());
|
||||
|
||||
events.expectRegister(null, "email").removeDetail("username").error("invalid_registration").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -148,6 +171,9 @@ public class RegisterTest {
|
|||
registerPage.register("firstName", "lastName", "email", "registerUserSuccess", "password", "password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
String userId = events.expectRegister("registerUserSuccess", "email").assertEvent().getUserId();
|
||||
events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,12 +25,14 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
|
@ -46,6 +48,7 @@ import org.openqa.selenium.WebDriver;
|
|||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -60,14 +63,19 @@ public class ResetPasswordTest {
|
|||
user.setEmail("login@test.com");
|
||||
user.setEnabled(true);
|
||||
|
||||
userId = user.getId();
|
||||
|
||||
UserCredentialModel creds = new UserCredentialModel();
|
||||
creds.setType(CredentialRepresentation.PASSWORD);
|
||||
creds.setValue("password");
|
||||
|
||||
appRealm.updateCredential(user, creds);
|
||||
appRealm.setAuditListeners(Collections.singleton("dummy"));
|
||||
}
|
||||
}));
|
||||
|
||||
private static String userId;
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -92,17 +100,31 @@ public class ResetPasswordTest {
|
|||
@WebResource
|
||||
protected LoginPasswordUpdatePage updatePasswordPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Test
|
||||
public void resetPassword() throws IOException, MessagingException {
|
||||
resetPassword("login-test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetPasswordByEmail() throws IOException, MessagingException {
|
||||
resetPassword("login@test.com");
|
||||
}
|
||||
|
||||
private void resetPassword(String username) throws IOException, MessagingException {
|
||||
loginPage.open();
|
||||
loginPage.resetPassword();
|
||||
|
||||
resetPasswordPage.assertCurrent();
|
||||
|
||||
resetPasswordPage.changePassword("login-test");
|
||||
resetPasswordPage.changePassword(username);
|
||||
|
||||
resetPasswordPage.assertCurrent();
|
||||
|
||||
events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
|
||||
|
||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
@ -118,50 +140,21 @@ public class ResetPasswordTest {
|
|||
|
||||
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
||||
|
||||
events.expectRequiredAction("update_password").user(userId).detail(Details.USERNAME, username).assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, username).assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("login-test", "resetPassword");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetPasswordByEmail() throws IOException, MessagingException {
|
||||
loginPage.open();
|
||||
loginPage.resetPassword();
|
||||
|
||||
resetPasswordPage.assertCurrent();
|
||||
|
||||
resetPasswordPage.changePassword("login@test.com");
|
||||
|
||||
resetPasswordPage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||
|
||||
String body = (String) message.getContent();
|
||||
String changePasswordUrl = body.split("\n")[3];
|
||||
|
||||
driver.navigate().to(changePasswordUrl.trim());
|
||||
|
||||
updatePasswordPage.assertCurrent();
|
||||
|
||||
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("login@test.com", "resetPassword");
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
}
|
||||
|
@ -182,6 +175,8 @@ public class ResetPasswordTest {
|
|||
Thread.sleep(1000);
|
||||
|
||||
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
|
||||
|
||||
events.expectRequiredAction("send_reset_password").user((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -211,6 +206,8 @@ public class ResetPasswordTest {
|
|||
String body = (String) message.getContent();
|
||||
String changePasswordUrl = body.split("\n")[3];
|
||||
|
||||
events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
|
||||
|
||||
driver.navigate().to(changePasswordUrl.trim());
|
||||
|
||||
updatePasswordPage.assertCurrent();
|
||||
|
@ -221,14 +218,23 @@ public class ResetPasswordTest {
|
|||
|
||||
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
|
||||
|
||||
events.expectRequiredAction("update_password").user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
loginPage.login("login-test", "resetPasswordWithPasswordPolicy");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
|
@ -63,6 +65,9 @@ public class SSOTest {
|
|||
@WebResource
|
||||
protected AccountUpdateProfilePage profilePage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Test
|
||||
public void loginSuccess() {
|
||||
loginPage.open();
|
||||
|
@ -71,6 +76,8 @@ public class SSOTest {
|
|||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
|
||||
appPage.open();
|
||||
|
||||
oauth.openLoginForm();
|
||||
|
@ -80,6 +87,9 @@ public class SSOTest {
|
|||
profilePage.open();
|
||||
|
||||
Assert.assertTrue(profilePage.isCurrent());
|
||||
|
||||
events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent();
|
||||
events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("account").detail(Details.REDIRECT_URI, "http://localhost:8081/auth/rest/realms/test/account/login-redirect").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -59,10 +62,15 @@ public class AccessTokenTest {
|
|||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
|
@ -82,6 +90,28 @@ public class AccessTokenTest {
|
|||
|
||||
Assert.assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
Assert.assertTrue(token.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
|
||||
|
||||
Event event = events.expectCodeToToken(codeId).assertEvent();
|
||||
Assert.assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
|
||||
Assert.assertEquals(oauth.verifyRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
|
||||
|
||||
response = oauth.doAccessTokenRequest(code, "password");
|
||||
Assert.assertEquals(400, response.getStatusCode());
|
||||
|
||||
events.expectCodeToToken(codeId).error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenInvalidClientCredentials() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, "invalid");
|
||||
Assert.assertEquals(400, response.getStatusCode());
|
||||
|
||||
events.expectCodeToToken(codeId).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,13 +26,14 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
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;
|
||||
|
@ -62,8 +63,8 @@ public class AuthorizationCodeTest {
|
|||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@WebResource
|
||||
protected ErrorPage errorPage;
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Test
|
||||
public void authorizationRequest() throws IOException {
|
||||
|
@ -77,6 +78,9 @@ public class AuthorizationCodeTest {
|
|||
Assert.assertNull(response.getError());
|
||||
|
||||
oauth.verifyCode(response.getCode());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Assert.assertEquals(codeId, new JWSInput(response.getCode()).readContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -90,6 +94,9 @@ public class AuthorizationCodeTest {
|
|||
|
||||
String code = driver.findElement(By.id(OAuth2Constants.CODE)).getText();
|
||||
oauth.verifyCode(code);
|
||||
|
||||
String codeId = events.expectLogin().detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Assert.assertEquals(codeId, new JWSInput(code).readContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -109,6 +116,9 @@ public class AuthorizationCodeTest {
|
|||
Assert.assertNotNull(response.getCode());
|
||||
|
||||
oauth.verifyCode(response.getCode());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Assert.assertEquals(codeId, new JWSInput(response.getCode()).readContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -121,6 +131,9 @@ public class AuthorizationCodeTest {
|
|||
Assert.assertNull(response.getError());
|
||||
|
||||
oauth.verifyCode(response.getCode());
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Assert.assertEquals(codeId, new JWSInput(response.getCode()).readContentAsString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,15 +21,14 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
|
@ -38,6 +37,9 @@ import org.keycloak.testsuite.rule.WebResource;
|
|||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
|
||||
*/
|
||||
|
@ -46,6 +48,9 @@ public class OAuthGrantTest {
|
|||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
|
@ -76,6 +81,9 @@ public class OAuthGrantTest {
|
|||
grantPage.accept();
|
||||
|
||||
Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
|
||||
|
||||
String codeId = events.expectLogin().client("third-party").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
|
||||
AccessToken token = oauth.verifyToken(accessToken.getAccessToken());
|
||||
|
@ -88,6 +96,8 @@ public class OAuthGrantTest {
|
|||
Assert.assertEquals(1, resourceAccess.size());
|
||||
Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
|
||||
Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
|
||||
|
||||
events.expectCodeToToken(codeId).client("third-party").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -103,5 +113,8 @@ public class OAuthGrantTest {
|
|||
|
||||
Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.ERROR));
|
||||
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
|
||||
events.expectLogin().client("third-party").error("rejected_by_user").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,8 +26,11 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -61,10 +64,15 @@ public class RefreshTokenTest {
|
|||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Test
|
||||
public void refreshTokenRequest() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
|
@ -72,6 +80,8 @@ public class RefreshTokenTest {
|
|||
String refreshTokenString = tokenResponse.getRefreshToken();
|
||||
RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
|
||||
|
||||
Event tokenEvent = events.expectCodeToToken(codeId).assertEvent();
|
||||
|
||||
Assert.assertNotNull(refreshTokenString);
|
||||
|
||||
Assert.assertEquals("bearer", tokenResponse.getTokenType());
|
||||
|
@ -106,6 +116,10 @@ public class RefreshTokenTest {
|
|||
|
||||
Assert.assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
Assert.assertTrue(refreshedToken.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
|
||||
|
||||
Event refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID)).assertEvent();
|
||||
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
|
||||
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import io.undertow.servlet.api.ServletInfo;
|
|||
import io.undertow.servlet.api.WebResourceCollection;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.keycloak.models.Config;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.ModelToRepresentation;
|
||||
|
@ -40,7 +40,8 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
public UserRepresentation getUser(String realm, String name) {
|
||||
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
|
||||
try {
|
||||
return ModelToRepresentation.toRepresentation(session.getRealmByName(realm).getUser(name));
|
||||
UserModel user = session.getRealmByName(realm).getUser(name);
|
||||
return user != null ? ModelToRepresentation.toRepresentation(user) : null;
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
|
|
|
@ -22,12 +22,8 @@
|
|||
package org.keycloak.testsuite.rule;
|
||||
|
||||
import org.keycloak.models.Config;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.ModelToRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.ApplicationServlet;
|
||||
|
||||
|
|
|
@ -27,13 +27,12 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.AccountRoles;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.DummySocialServlet;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||
|
@ -87,6 +86,9 @@ public class SocialLoginTest {
|
|||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@BeforeClass
|
||||
public static void before() {
|
||||
keycloakRule.deployServlet("dummy-social", "/dummy-social", DummySocialServlet.class);
|
||||
|
@ -107,8 +109,21 @@ public class SocialLoginTest {
|
|||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
String userId = events.expect("register")
|
||||
.user(AssertEvents.isUUID())
|
||||
.detail(Details.EMAIL, "bob@builder.com")
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REGISTER_METHOD, "social")
|
||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||
.detail(Details.USERNAME, "1@dummy")
|
||||
.assertEvent().getUserId();
|
||||
|
||||
String codeId = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
|
||||
events.expectCodeToToken(codeId).user(userId).assertEvent();
|
||||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
Assert.assertEquals(36, token.getSubject().length());
|
||||
|
||||
|
@ -118,8 +133,21 @@ public class SocialLoginTest {
|
|||
Assert.assertEquals("Bob", profile.getFirstName());
|
||||
Assert.assertEquals("Builder", profile.getLastName());
|
||||
Assert.assertEquals("bob@builder.com", profile.getEmail());
|
||||
}
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
loginPage.clickSocial("dummy");
|
||||
|
||||
driver.findElement(By.id("id")).sendKeys("1");
|
||||
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").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginCancelled() throws Exception {
|
||||
|
@ -132,9 +160,13 @@ public class SocialLoginTest {
|
|||
Assert.assertTrue(loginPage.isCurrent());
|
||||
Assert.assertEquals("Access denied", loginPage.getWarning());
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).detail(Details.AUTH_METHOD, "social").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -164,13 +196,29 @@ public class SocialLoginTest {
|
|||
Assert.assertEquals("Builder", profilePage.getLastName());
|
||||
Assert.assertEquals("bob@builder.com", profilePage.getEmail());
|
||||
|
||||
String userId = events.expect("register")
|
||||
.user(AssertEvents.isUUID())
|
||||
.detail(Details.EMAIL, "bob@builder.com")
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.REGISTER_METHOD, "social")
|
||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||
.detail(Details.USERNAME, "2@dummy")
|
||||
.assertEvent().getUserId();
|
||||
|
||||
profilePage.update("Dummy", "User", "dummy-user-reg@dummy-social");
|
||||
|
||||
events.expectRequiredAction("update_profile").user(userId).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").assertEvent();
|
||||
events.expectRequiredAction("update_email").user(userId).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").detail(Details.PREVIOUS_EMAIL, "bob@builder.com").detail(Details.UPDATED_EMAIL, "dummy-user-reg@dummy-social").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
String codeId = events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
events.expectCodeToToken(codeId).user(userId).assertEvent();
|
||||
|
||||
UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
|
||||
|
||||
Assert.assertEquals("Dummy", profile.getFirstName());
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.testsuite.AssertEvents
|
Loading…
Reference in a new issue