Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c07f7bb1e |
No files matched your search
@@ -1,20 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
*.html text eol=lf
|
||||
*.java text eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.jsp text eol=lf
|
||||
*.md text eol=lf
|
||||
*.properties text eol=lf
|
||||
*.svg text auto
|
||||
*.xml text eol=lf
|
||||
*.xsl text eol=lf
|
||||
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.otf binary
|
||||
*.woff binary
|
||||
@@ -9,12 +9,6 @@
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
catalog.xml
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
language: java
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
install:
|
||||
- travis_wait mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||
|
||||
script:
|
||||
- mvn test -B
|
||||
- mvn -file testsuite/integration-arquillian test -B -Pno-console
|
||||
|
||||
sudo: false
|
||||
@@ -1,72 +1,4 @@
|
||||
Keycloak
|
||||
keycloak
|
||||
========
|
||||
|
||||
Keycloak is an SSO Service for web apps and REST services. For more information visit [http://keycloak.org](http://keycloak.org).
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Ensure you have JDK 7 (or newer), Maven 3.2.1 (or newer) and Git installed
|
||||
|
||||
java -version
|
||||
mvn -version
|
||||
git --version
|
||||
|
||||
First clone the Keycloak repository:
|
||||
|
||||
git clone https://github.com/keycloak/keycloak.git
|
||||
cd keycloak
|
||||
|
||||
To build Keycloak run:
|
||||
|
||||
mvn install
|
||||
|
||||
This will build all modules and run the testsuite.
|
||||
|
||||
To build the distribution run:
|
||||
|
||||
mvn install -Pdistribution
|
||||
|
||||
Once completed you will find distribution archives in `distribution`.
|
||||
|
||||
|
||||
Starting Keycloak
|
||||
-----------------
|
||||
|
||||
To start Keycloak during development first build as specficied above, then run:
|
||||
|
||||
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
|
||||
|
||||
|
||||
To start Keycloak from the appliance distribution first build the distribution it as specified above, then run:
|
||||
|
||||
tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
|
||||
cd keycloak-appliance-dist-all-<VERSION>/keycloak
|
||||
bin/standalone.sh
|
||||
|
||||
To stop the server press `Ctrl + C`.
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
* See [Hacking on Keycloak](misc/HackingOnKeycloak.md)
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
* [User Guide, Admin REST API and Javadocs](http://keycloak.jboss.org/docs)
|
||||
* Developer documentation
|
||||
* [Hacking on Keycloak](misc/HackingOnKeycloak.md) - how to become a Keycloak contributor
|
||||
* [Testsuite](misc/Testsuite.md) - details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
|
||||
* [Database Testing](misc/DatabaseTesting.md) - how to do testing of Keycloak on different databases
|
||||
* [Updating Database](misc/UpdatingDatabaseSchema.md) - how to change the Keycloak database
|
||||
* [Release Process](misc/ReleaseProcess.md) - how to release Keycloak
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
SSO Service for web apps and REST services.
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</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>
|
||||
<scope>provided</scope>
|
||||
</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>
|
||||
@@ -0,0 +1,149 @@
|
||||
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 org.keycloak.models.UserSessionModel;
|
||||
|
||||
import java.util.HashMap;
|
||||
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 Audit(List<AuditListener> listeners, RealmModel realm, String ipAddress) {
|
||||
this.listeners = listeners;
|
||||
this.event = new Event();
|
||||
|
||||
realm(realm);
|
||||
ipAddress(ipAddress);
|
||||
}
|
||||
|
||||
Audit() {
|
||||
}
|
||||
|
||||
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 session(UserSessionModel session) {
|
||||
event.setSessionId(session.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit session(String sessionId) {
|
||||
event.setSessionId(sessionId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit ipAddress(String ipAddress) {
|
||||
event.setIpAddress(ipAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit event(EventType 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.setEvent(EventType.valueOf(event.getEvent().name() + "_ERROR"));
|
||||
event.setError(error);
|
||||
send();
|
||||
}
|
||||
|
||||
public Audit clone() {
|
||||
Audit clone = new Audit();
|
||||
clone.listeners = listeners;
|
||||
clone.event = event.clone();
|
||||
return 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;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface AuditListener extends Provider {
|
||||
|
||||
public void onEvent(Event event);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface AuditListenerFactory extends ProviderFactory<AuditListener> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AuditListenerSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "audit-listener";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return AuditListener.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return AuditListenerFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface AuditProvider extends AuditListener {
|
||||
|
||||
public EventQuery createQuery();
|
||||
|
||||
public void clear();
|
||||
|
||||
public void clear(String realmId);
|
||||
|
||||
public void clear(String realmId, long olderThan);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface AuditProviderFactory extends ProviderFactory<AuditProvider> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AuditProviderSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "audit";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return AuditProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return AuditProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
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 USER_TEMPORARILY_DISABLED = "user_temporarily_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";
|
||||
|
||||
String USER_NOT_LOGGED_IN = "user_not_logged_in";
|
||||
String USER_SESSION_NOT_FOUND = "user_session_not_found";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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 EventType event;
|
||||
|
||||
private String realmId;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String sessionId;
|
||||
|
||||
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 EventType getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(EventType 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 getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
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.sessionId = sessionId;
|
||||
clone.ipAddress = ipAddress;
|
||||
clone.error = error;
|
||||
clone.details = details != null ? new HashMap<String, String>(details) : null;
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface EventQuery {
|
||||
|
||||
public EventQuery event(EventType... events);
|
||||
|
||||
public EventQuery realm(String realmId);
|
||||
|
||||
public EventQuery client(String clientId);
|
||||
|
||||
public EventQuery user(String userId);
|
||||
|
||||
public EventQuery ipAddress(String ipAddress);
|
||||
|
||||
public EventQuery firstResult(int result);
|
||||
|
||||
public EventQuery maxResults(int results);
|
||||
|
||||
public List<Event> getResultList();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.keycloak.audit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public enum EventType {
|
||||
|
||||
LOGIN,
|
||||
LOGIN_ERROR,
|
||||
REGISTER,
|
||||
REGISTER_ERROR,
|
||||
LOGOUT,
|
||||
LOGOUT_ERROR,
|
||||
CODE_TO_TOKEN,
|
||||
CODE_TO_TOKEN_ERROR,
|
||||
REFRESH_TOKEN,
|
||||
REFRESH_TOKEN_ERROR,
|
||||
SOCIAL_LINK,
|
||||
SOCIAL_LINK_ERROR,
|
||||
REMOVE_SOCIAL_LINK,
|
||||
REMOVE_SOCIAL_LINK_ERROR,
|
||||
|
||||
UPDATE_EMAIL,
|
||||
UPDATE_EMAIL_ERROR,
|
||||
UPDATE_PROFILE,
|
||||
UPDATE_PROFILE_ERROR,
|
||||
UPDATE_PASSWORD,
|
||||
UPDATE_PASSWORD_ERROR,
|
||||
UPDATE_TOTP,
|
||||
UPDATE_TOTP_ERROR,
|
||||
VERIFY_EMAIL,
|
||||
VERIFY_EMAIL_ERROR,
|
||||
|
||||
REMOVE_TOTP,
|
||||
REMOVE_TOTP_ERROR,
|
||||
|
||||
SEND_VERIFY_EMAIL,
|
||||
SEND_VERIFY_EMAIL_ERROR,
|
||||
SEND_RESET_PASSWORD,
|
||||
SEND_RESET_PASSWORD_ERROR
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.keycloak.audit.AuditProviderSpi
|
||||
org.keycloak.audit.AuditListenerSpi
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-audit-email</artifactId>
|
||||
<name>Keycloak Audit Email Provider</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<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>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-email-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.keycloak.audit.email;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.audit.EventType;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class EmailAuditListener implements AuditListener {
|
||||
|
||||
private static final Logger log = Logger.getLogger(EmailAuditListener.class);
|
||||
|
||||
private KeycloakSession keycloakSession;
|
||||
private EmailProvider emailProvider;
|
||||
private Set<EventType> includedEvents;
|
||||
|
||||
public EmailAuditListener(KeycloakSession keycloakSession, EmailProvider emailProvider, Set<EventType> includedEvents) {
|
||||
this.keycloakSession = keycloakSession;
|
||||
this.emailProvider = emailProvider;
|
||||
this.includedEvents = includedEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
if (includedEvents.contains(event.getEvent())) {
|
||||
if (event.getRealmId() != null && event.getUserId() != null) {
|
||||
RealmModel realm = keycloakSession.getRealm(event.getRealmId());
|
||||
UserModel user = realm.getUserById(event.getUserId());
|
||||
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
|
||||
try {
|
||||
emailProvider.setRealm(realm).setUser(user).sendEvent(event);
|
||||
} catch (EmailException e) {
|
||||
log.error("Failed to send event mail", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.keycloak.audit.email;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.audit.AuditListenerFactory;
|
||||
import org.keycloak.audit.EventType;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class EmailAuditListenerFactory implements AuditListenerFactory {
|
||||
|
||||
private static final Set<EventType> SUPPORTED_EVENTS = new HashSet<EventType>();
|
||||
static {
|
||||
Collections.addAll(SUPPORTED_EVENTS, EventType.LOGIN_ERROR, EventType.UPDATE_PASSWORD, EventType.REMOVE_TOTP, EventType.UPDATE_TOTP);
|
||||
}
|
||||
|
||||
private Set<EventType> includedEvents = new HashSet<EventType>();
|
||||
|
||||
@Override
|
||||
public AuditListener create(ProviderSession providerSession) {
|
||||
KeycloakSession keycloakSession = providerSession.getProvider(KeycloakSession.class);
|
||||
EmailProvider emailProvider = providerSession.getProvider(EmailProvider.class);
|
||||
return new EmailAuditListener(keycloakSession, emailProvider, includedEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
String[] include = config.getArray("include-events");
|
||||
if (include != null) {
|
||||
for (String i : include) {
|
||||
includedEvents.add(EventType.valueOf(i.toUpperCase()));
|
||||
}
|
||||
} else {
|
||||
includedEvents.addAll(SUPPORTED_EVENTS);
|
||||
}
|
||||
|
||||
String[] exclude = config.getArray("exclude-events");
|
||||
if (exclude != null) {
|
||||
for (String e : exclude) {
|
||||
includedEvents.remove(EventType.valueOf(e.toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "email";
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.audit.email.EmailAuditListenerFactory
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</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>
|
||||
<scope>provided</scope>
|
||||
</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-audit-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
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 final Logger logger;
|
||||
|
||||
public JBossLoggingAuditListener(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
Logger.Level level = event.getError() != null ? 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.getError() != null) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.keycloak.audit.log;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.audit.AuditListenerFactory;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JBossLoggingAuditListenerFactory implements AuditListenerFactory {
|
||||
|
||||
public static final String ID = "jboss-logging";
|
||||
|
||||
private static final Logger logger = Logger.getLogger("org.keycloak.audit");
|
||||
|
||||
@Override
|
||||
public AuditListener create(ProviderSession providerSession) {
|
||||
return new JBossLoggingAuditListener(logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.audit.log.JBossLoggingAuditListenerFactory
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</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>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-audit-tests</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.javax.persistence</groupId>
|
||||
<artifactId>hibernate-jpa-2.0-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-entitymanager</artifactId>
|
||||
<version>${hibernate.entitymanager.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+7
-19
@@ -1,47 +1,35 @@
|
||||
package org.keycloak.events.jpa;
|
||||
package org.keycloak.audit.jpa;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Entity
|
||||
@Table(name="EVENT_ENTITY")
|
||||
public class EventEntity {
|
||||
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
private String id;
|
||||
|
||||
@Column(name="EVENT_TIME")
|
||||
private long time;
|
||||
|
||||
@Column(name="TYPE")
|
||||
private String type;
|
||||
private String event;
|
||||
|
||||
@Column(name="REALM_ID")
|
||||
private String realmId;
|
||||
|
||||
@Column(name="CLIENT_ID")
|
||||
private String clientId;
|
||||
|
||||
@Column(name="USER_ID")
|
||||
private String userId;
|
||||
|
||||
@Column(name="SESSION_ID")
|
||||
private String sessionId;
|
||||
|
||||
@Column(name="IP_ADDRESS")
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name="ERROR")
|
||||
private String error;
|
||||
|
||||
@Column(name="DETAILS_JSON", length = 2550)
|
||||
@Column(length = 2550)
|
||||
private String detailsJson;
|
||||
|
||||
public String getId() {
|
||||
@@ -60,12 +48,12 @@ public class EventEntity {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
public String getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
public void setEvent(String event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.keycloak.audit.jpa;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.type.TypeReference;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.audit.EventQuery;
|
||||
import org.keycloak.audit.EventType;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JpaAuditProvider implements AuditProvider {
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
private static final TypeReference<Map<String, String>> mapType = new TypeReference<Map<String, String>>() {
|
||||
};
|
||||
private static final Logger logger = Logger.getLogger(JpaAuditProvider.class);
|
||||
|
||||
private EntityManager em;
|
||||
private EntityTransaction tx;
|
||||
private Set<EventType> includedEvents;
|
||||
|
||||
public JpaAuditProvider(EntityManager em, Set<EventType> includedEvents) {
|
||||
this.em = em;
|
||||
this.includedEvents = includedEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery createQuery() {
|
||||
return new JpaEventQuery(em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
beginTx();
|
||||
em.createQuery("delete from EventEntity").executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String realmId) {
|
||||
beginTx();
|
||||
em.createQuery("delete from EventEntity where realmId = :realmId").setParameter("realmId", realmId).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String realmId, long olderThan) {
|
||||
beginTx();
|
||||
em.createQuery("delete from EventEntity where realmId = :realmId and time < :time").setParameter("realmId", realmId).setParameter("time", olderThan).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
if (includedEvents.contains(event.getEvent())) {
|
||||
beginTx();
|
||||
em.persist(convert(event));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (tx != null) {
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
em.close();
|
||||
}
|
||||
|
||||
private void beginTx() {
|
||||
if (tx == null) {
|
||||
tx = em.getTransaction();
|
||||
tx.begin();
|
||||
}
|
||||
}
|
||||
|
||||
static EventEntity convert(Event o) {
|
||||
EventEntity e = new EventEntity();
|
||||
e.setId(UUID.randomUUID().toString());
|
||||
e.setTime(o.getTime());
|
||||
e.setEvent(o.getEvent().toString());
|
||||
e.setRealmId(o.getRealmId());
|
||||
e.setClientId(o.getClientId());
|
||||
e.setUserId(o.getUserId());
|
||||
e.setSessionId(o.getSessionId());
|
||||
e.setIpAddress(o.getIpAddress());
|
||||
e.setError(o.getError());
|
||||
try {
|
||||
e.setDetailsJson(mapper.writeValueAsString(o.getDetails()));
|
||||
} catch (IOException ex) {
|
||||
logger.error("Failed to write log details", ex);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event convert(EventEntity o) {
|
||||
Event e = new Event();
|
||||
e.setTime(o.getTime());
|
||||
e.setEvent(EventType.valueOf(o.getEvent()));
|
||||
e.setRealmId(o.getRealmId());
|
||||
e.setClientId(o.getClientId());
|
||||
e.setUserId(o.getUserId());
|
||||
e.setSessionId(o.getSessionId());
|
||||
e.setIpAddress(o.getIpAddress());
|
||||
e.setError(o.getError());
|
||||
try {
|
||||
Map<String, String> details = mapper.readValue(o.getDetailsJson(), mapType);
|
||||
e.setDetails(details);
|
||||
} catch (IOException ex) {
|
||||
logger.error("Failed to read log details", ex);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.keycloak.audit.jpa;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.audit.AuditProviderFactory;
|
||||
import org.keycloak.audit.EventType;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.util.JpaUtils;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JpaAuditProviderFactory implements AuditProviderFactory {
|
||||
|
||||
public static final String ID = "jpa";
|
||||
private EntityManagerFactory emf;
|
||||
|
||||
private Set<EventType> includedEvents = new HashSet<EventType>();
|
||||
|
||||
@Override
|
||||
public AuditProvider create(ProviderSession providerSession) {
|
||||
return new JpaAuditProvider(emf.createEntityManager(), includedEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
emf = Persistence.createEntityManagerFactory("jpa-keycloak-audit-store", JpaUtils.getHibernateProperties());
|
||||
|
||||
String[] include = config.getArray("include-events");
|
||||
if (include != null) {
|
||||
for (String i : include) {
|
||||
includedEvents.add(EventType.valueOf(i.toUpperCase()));
|
||||
}
|
||||
} else {
|
||||
for (EventType i : EventType.values()) {
|
||||
includedEvents.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
String[] exclude = config.getArray("exclude-events");
|
||||
if (exclude != null) {
|
||||
for (String e : exclude) {
|
||||
includedEvents.remove(EventType.valueOf(e.toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
emf.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
||||
+8
-22
@@ -1,8 +1,8 @@
|
||||
package org.keycloak.events.jpa;
|
||||
package org.keycloak.audit.jpa;
|
||||
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventQuery;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.audit.EventQuery;
|
||||
import org.keycloak.audit.EventType;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
@@ -10,9 +10,7 @@ import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -39,12 +37,12 @@ public class JpaEventQuery implements EventQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery type(EventType... types) {
|
||||
public EventQuery event(EventType... events) {
|
||||
List<String> eventStrings = new LinkedList<String>();
|
||||
for (EventType e : types) {
|
||||
for (EventType e : events) {
|
||||
eventStrings.add(e.toString());
|
||||
}
|
||||
predicates.add(root.get("type").in(eventStrings));
|
||||
predicates.add(root.get("event").in(eventStrings));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -66,18 +64,6 @@ public class JpaEventQuery implements EventQuery {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery fromDate(Date fromDate) {
|
||||
predicates.add(cb.greaterThanOrEqualTo(root.<Long>get("time"), fromDate.getTime()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery toDate(Date toDate) {
|
||||
predicates.add(cb.lessThanOrEqualTo(root.<Long>get("time"), toDate.getTime()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery ipAddress(String ipAddress) {
|
||||
predicates.add(cb.equal(root.get("ipAddress"), ipAddress));
|
||||
@@ -116,7 +102,7 @@ public class JpaEventQuery implements EventQuery {
|
||||
|
||||
List<Event> events = new LinkedList<Event>();
|
||||
for (EventEntity e : query.getResultList()) {
|
||||
events.add(JpaEventStoreProvider.convertEvent(e));
|
||||
events.add(JpaAuditProvider.convert(e));
|
||||
}
|
||||
|
||||
return events;
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.audit.jpa.JpaAuditProviderFactory
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.keycloak.audit.jpa;
|
||||
|
||||
import org.keycloak.audit.tests.AbstractAuditProviderTest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JpaAuditProviderTest extends AbstractAuditProviderTest {
|
||||
|
||||
@Override
|
||||
protected String getProviderId() {
|
||||
return JpaAuditProviderFactory.ID;
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
|
||||
version="1.0">
|
||||
<persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
|
||||
<provider>org.hibernate.ejb.HibernatePersistence</provider>
|
||||
|
||||
<class>org.keycloak.audit.jpa.EventEntity</class>
|
||||
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
|
||||
<properties>
|
||||
<property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
|
||||
<property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
|
||||
<property name="hibernate.connection.username" value="sa"/>
|
||||
<property name="hibernate.connection.password" value=""/>
|
||||
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
|
||||
<property name="hibernate.show_sql" value="false" />
|
||||
<property name="hibernate.format_sql" value="true" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-audit-mongo</artifactId>
|
||||
<name>Keycloak Audit Mongo Provider</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<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-audit-tests</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongo-java-driver</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<keycloak.audit.mongo.host>localhost</keycloak.audit.mongo.host>
|
||||
<keycloak.audit.mongo.port>27018</keycloak.audit.mongo.port>
|
||||
<keycloak.audit.mongo.db>keycloak</keycloak.audit.mongo.db>
|
||||
<keycloak.audit.mongo.clearOnStartup>true</keycloak.audit.mongo.clearOnStartup>
|
||||
<keycloak.audit.mongo.bindIp>127.0.0.1</keycloak.audit.mongo.bindIp>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<keycloak.audit.mongo.host>${keycloak.audit.mongo.host}</keycloak.audit.mongo.host>
|
||||
<keycloak.audit.mongo.port>${keycloak.audit.mongo.port}</keycloak.audit.mongo.port>
|
||||
<keycloak.audit.mongo.db>${keycloak.audit.mongo.db}</keycloak.audit.mongo.db>
|
||||
<keycloak.audit.mongo.clearOnStartup>${keycloak.audit.mongo.clearOnStartup}</keycloak.audit.mongo.clearOnStartup>
|
||||
<keycloak.audit.mongo.bindIp>${keycloak.model.mongo.bindIp}</keycloak.audit.mongo.bindIp>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-test</id>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Embedded mongo -->
|
||||
<plugin>
|
||||
<groupId>com.github.joelittlejohn.embedmongo</groupId>
|
||||
<artifactId>embedmongo-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>start-mongodb</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<port>${keycloak.audit.mongo.port}</port>
|
||||
<logging>file</logging>
|
||||
<logFile>${project.build.directory}/mongodb.log</logFile>
|
||||
<bindIp>${keycloak.audit.mongo.bindIp}</bindIp>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>stop-mongodb</id>
|
||||
<phase>post-integration-test</phase>
|
||||
<goals>
|
||||
<goal>stop</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.keycloak.audit.mongo;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.audit.EventQuery;
|
||||
import org.keycloak.audit.EventType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoAuditProvider implements AuditProvider {
|
||||
|
||||
private DBCollection audit;
|
||||
private Set<EventType> includedEvents;
|
||||
|
||||
public MongoAuditProvider(DBCollection audit, Set<EventType> includedEvents) {
|
||||
this.audit = audit;
|
||||
this.includedEvents = includedEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery createQuery() {
|
||||
return new MongoEventQuery(audit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
audit.remove(new BasicDBObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String realmId) {
|
||||
audit.remove(new BasicDBObject("realmId", realmId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(String realmId, long olderThan) {
|
||||
BasicDBObject q = new BasicDBObject();
|
||||
q.put("realmId", realmId);
|
||||
q.put("time", new BasicDBObject("$lt", olderThan));
|
||||
audit.remove(q);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
if (includedEvents.contains(event.getEvent())) {
|
||||
audit.insert(convert(event));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
static DBObject convert(Event o) {
|
||||
BasicDBObject e = new BasicDBObject();
|
||||
e.put("time", o.getTime());
|
||||
e.put("event", o.getEvent().toString());
|
||||
e.put("realmId", o.getRealmId());
|
||||
e.put("clientId", o.getClientId());
|
||||
e.put("userId", o.getUserId());
|
||||
e.put("sessionId", o.getSessionId());
|
||||
e.put("ipAddress", o.getIpAddress());
|
||||
e.put("error", o.getError());
|
||||
|
||||
BasicDBObject details = new BasicDBObject();
|
||||
if (o.getDetails() != null) {
|
||||
for (Map.Entry<String, String> entry : o.getDetails().entrySet()) {
|
||||
details.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
e.put("details", details);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static Event convert(BasicDBObject o) {
|
||||
Event e = new Event();
|
||||
e.setTime(o.getLong("time"));
|
||||
e.setEvent(EventType.valueOf(o.getString("event")));
|
||||
e.setRealmId(o.getString("realmId"));
|
||||
e.setClientId(o.getString("clientId"));
|
||||
e.setUserId(o.getString("userId"));
|
||||
e.setSessionId(o.getString("sessionId"));
|
||||
e.setIpAddress(o.getString("ipAddress"));
|
||||
e.setError(o.getString("error"));
|
||||
|
||||
BasicDBObject d = (BasicDBObject) o.get("details");
|
||||
if (d != null) {
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
for (Object k : d.keySet()) {
|
||||
details.put((String) k, d.getString((String) k));
|
||||
}
|
||||
e.setDetails(details);
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.keycloak.audit.mongo;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
import com.mongodb.WriteConcern;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.audit.AuditProviderFactory;
|
||||
import org.keycloak.audit.EventType;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoAuditProviderFactory implements AuditProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(MongoAuditProviderFactory.class);
|
||||
|
||||
public static final String ID = "mongo";
|
||||
private MongoClient client;
|
||||
private DB db;
|
||||
|
||||
private Set<EventType> includedEvents = new HashSet<EventType>();
|
||||
|
||||
@Override
|
||||
public AuditProvider create(ProviderSession providerSession) {
|
||||
return new MongoAuditProvider(db.getCollection("audit"), includedEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
try {
|
||||
String host = config.get("host", ServerAddress.defaultHost());
|
||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||
String dbName = config.get("db", "keycloak-audit");
|
||||
boolean clearOnStartup = config.getBoolean("clearOnStartup", false);
|
||||
|
||||
String user = config.get("user");
|
||||
String password = config.get("password");
|
||||
if (user != null && password != null) {
|
||||
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
||||
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential));
|
||||
} else {
|
||||
client = new MongoClient(host, port);
|
||||
}
|
||||
|
||||
client.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
|
||||
db = client.getDB(dbName);
|
||||
|
||||
if (clearOnStartup) {
|
||||
db.getCollection("audit").drop();
|
||||
}
|
||||
|
||||
logger.infof("Initialized mongo audit. host: %s, port: %d, db: %s, clearOnStartup: %b", host, port, dbName, clearOnStartup);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
String[] include = config.getArray("include-events");
|
||||
if (include != null) {
|
||||
for (String i : include) {
|
||||
includedEvents.add(EventType.valueOf(i.toUpperCase()));
|
||||
}
|
||||
} else {
|
||||
for (EventType i : EventType.values()) {
|
||||
includedEvents.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
String[] exclude = config.getArray("exclude-events");
|
||||
if (exclude != null) {
|
||||
for (String e : exclude) {
|
||||
includedEvents.remove(EventType.valueOf(e.toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
client.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.keycloak.audit.mongo;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.audit.EventQuery;
|
||||
import org.keycloak.audit.EventType;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoEventQuery implements EventQuery {
|
||||
|
||||
private Integer firstResult;
|
||||
private Integer maxResults;
|
||||
private DBCollection audit;
|
||||
private final BasicDBObject query;
|
||||
|
||||
public MongoEventQuery(DBCollection audit) {
|
||||
this.audit = audit;
|
||||
query = new BasicDBObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery event(EventType... events) {
|
||||
List<String> eventStrings = new LinkedList<String>();
|
||||
for (EventType e : events) {
|
||||
eventStrings.add(e.toString());
|
||||
}
|
||||
query.put("event", new BasicDBObject("$in", eventStrings));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery realm(String realmId) {
|
||||
query.put("realmId", realmId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery client(String clientId) {
|
||||
query.put("clientId", clientId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery user(String userId) {
|
||||
query.put("userId", userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery ipAddress(String ipAddress) {
|
||||
query.put("ipAddress", ipAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery firstResult(int firstResult) {
|
||||
this.firstResult = firstResult;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery maxResults(int maxResults) {
|
||||
this.maxResults = maxResults;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Event> getResultList() {
|
||||
DBCursor cur = audit.find(query).sort(new BasicDBObject("time", -1));
|
||||
if (firstResult != null) {
|
||||
cur.skip(firstResult);
|
||||
}
|
||||
if (maxResults != null) {
|
||||
cur.limit(maxResults);
|
||||
}
|
||||
|
||||
List<Event> events = new LinkedList<Event>();
|
||||
while (cur.hasNext()) {
|
||||
events.add(MongoAuditProvider.convert((BasicDBObject) cur.next()));
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.audit.mongo.MongoAuditProviderFactory
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.keycloak.audit.mongo;
|
||||
|
||||
import org.keycloak.audit.tests.AbstractAuditProviderTest;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoAuditProviderTest extends AbstractAuditProviderTest {
|
||||
|
||||
@Override
|
||||
protected String getProviderId() {
|
||||
return MongoAuditProviderFactory.ID;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
<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-3</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>email</module>
|
||||
<module>jpa</module>
|
||||
<module>jboss-logging</module>
|
||||
<module>mongo</module>
|
||||
<module>tests</module>
|
||||
</modules>
|
||||
</project>
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-audit-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-audit-tests</artifactId>
|
||||
<name>Keycloak Audit Tests</name>
|
||||
<description/>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<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>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,139 @@
|
||||
package org.keycloak.audit.tests;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.audit.AuditProviderFactory;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.audit.EventType;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class AbstractAuditProviderTest {
|
||||
|
||||
private ProviderFactory<AuditProvider> factory;
|
||||
private AuditProvider provider;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
String providerId = getProviderId();
|
||||
ServiceLoader<AuditProviderFactory> factories = ServiceLoader.load(AuditProviderFactory.class);
|
||||
for (AuditProviderFactory f : factories) {
|
||||
if (f.getId().equals(providerId)) {
|
||||
factory = f;
|
||||
factory.init(Config.scope("audit", providerId));
|
||||
}
|
||||
}
|
||||
|
||||
provider = factory.create(null);
|
||||
}
|
||||
|
||||
protected abstract String getProviderId();
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
provider.clear();
|
||||
provider.close();
|
||||
factory.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
provider.onEvent(create(EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void query() {
|
||||
long oldest = System.currentTimeMillis() - 30000;
|
||||
long newest = System.currentTimeMillis() + 30000;
|
||||
|
||||
provider.onEvent(create(EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(newest, EventType.REGISTER, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(newest, EventType.REGISTER, "realmId", "clientId", "userId2", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(EventType.LOGIN, "realmId2", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(oldest, EventType.LOGIN, "realmId", "clientId2", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(EventType.LOGIN, "realmId", "clientId", "userId2", "127.0.0.1", "error"));
|
||||
|
||||
provider.close();
|
||||
provider = factory.create(null);
|
||||
|
||||
Assert.assertEquals(5, provider.createQuery().client("clientId").getResultList().size());
|
||||
Assert.assertEquals(5, provider.createQuery().realm("realmId").getResultList().size());
|
||||
Assert.assertEquals(4, provider.createQuery().event(EventType.LOGIN).getResultList().size());
|
||||
Assert.assertEquals(6, provider.createQuery().event(EventType.LOGIN, EventType.REGISTER).getResultList().size());
|
||||
Assert.assertEquals(4, provider.createQuery().user("userId").getResultList().size());
|
||||
|
||||
Assert.assertEquals(1, provider.createQuery().user("userId").event(EventType.REGISTER).getResultList().size());
|
||||
|
||||
Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
|
||||
Assert.assertEquals(1, provider.createQuery().firstResult(5).getResultList().size());
|
||||
|
||||
Assert.assertEquals(newest, provider.createQuery().maxResults(1).getResultList().get(0).getTime());
|
||||
Assert.assertEquals(oldest, provider.createQuery().firstResult(5).maxResults(1).getResultList().get(0).getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clear() {
|
||||
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis() - 20000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId2", "clientId", "userId", "127.0.0.1", "error"));
|
||||
|
||||
provider.close();
|
||||
provider = factory.create(null);
|
||||
|
||||
provider.clear("realmId");
|
||||
|
||||
Assert.assertEquals(1, provider.createQuery().getResultList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearOld() {
|
||||
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis() - 20000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId2", "clientId", "userId", "127.0.0.1", "error"));
|
||||
|
||||
provider.close();
|
||||
provider = factory.create(null);
|
||||
|
||||
provider.clear("realmId", System.currentTimeMillis() - 10000);
|
||||
|
||||
Assert.assertEquals(3, provider.createQuery().getResultList().size());
|
||||
}
|
||||
|
||||
private Event create(EventType event, String realmId, String clientId, String userId, String ipAddress, String error) {
|
||||
return create(System.currentTimeMillis(), event, realmId, clientId, userId, ipAddress, error);
|
||||
}
|
||||
|
||||
private Event create(long time, EventType event, String realmId, String clientId, String userId, String ipAddress, String error) {
|
||||
Event e = new Event();
|
||||
e.setTime(time);
|
||||
e.setEvent(event);
|
||||
e.setRealmId(realmId);
|
||||
e.setClientId(clientId);
|
||||
e.setUserId(userId);
|
||||
e.setIpAddress(ipAddress);
|
||||
e.setError(error);
|
||||
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
details.put("key1", "value1");
|
||||
details.put("key2", "value2");
|
||||
|
||||
e.setDetails(details);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-authentication-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-authentication-api</artifactId>
|
||||
<name>Keycloak Authentication SPI</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<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>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AuthProviderConstants {
|
||||
|
||||
// Model is default provider. See AuthenticationProviderModel.DEFAULT_PROVIDER
|
||||
public static final String PROVIDER_NAME_MODEL = "model";
|
||||
public static final String PROVIDER_NAME_EXTERNAL_MODEL = "externalModel";
|
||||
public static final String PROVIDER_NAME_PICKETLINK = "picketlink";
|
||||
|
||||
// Used in external-model provider
|
||||
public static final String EXTERNAL_REALM_ID = "externalRealmId";
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
/**
|
||||
* Result of authentication by AuthenticationProvider
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public enum AuthProviderStatus {
|
||||
|
||||
SUCCESS, INVALID_CREDENTIALS, FAILED
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AuthUser {
|
||||
|
||||
private String id;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
|
||||
private String providerName;
|
||||
|
||||
public AuthUser(String id, String username, String providerName) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.providerName = providerName;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public AuthUser setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public AuthUser setUsername(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public AuthUser setName(String name) {
|
||||
int i = name.lastIndexOf(' ');
|
||||
if (i != -1) {
|
||||
firstName = name.substring(0, i);
|
||||
lastName = name.substring(i + 1);
|
||||
} else {
|
||||
firstName = name;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthUser setName(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public AuthUser setEmail(String email) {
|
||||
this.email = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getProviderName() {
|
||||
return providerName;
|
||||
}
|
||||
|
||||
public AuthUser setProviderName(String providerName) {
|
||||
this.providerName = providerName;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface AuthenticationProvider extends Provider {
|
||||
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Get names of all available configuration options of current provider
|
||||
*
|
||||
* @return options or empty list if no options available
|
||||
*/
|
||||
List<String> getAvailableOptions();
|
||||
|
||||
/**
|
||||
* Get user by given username or email. Return user instance or null if user doesn't exists in this authentication provider
|
||||
*
|
||||
* @param realm
|
||||
* @param configuration
|
||||
* @param username or email
|
||||
* @return found user or null if user with given username doesn't exists
|
||||
* @throws AuthenticationProviderException
|
||||
*/
|
||||
AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException;
|
||||
|
||||
/**
|
||||
* Try to register user with this authentication provider
|
||||
*
|
||||
* @param realm
|
||||
* @param configuration
|
||||
* @param username
|
||||
* @return ID of newly created user (For example ID from LDAP)
|
||||
* @throws AuthenticationProviderException if user creation couldn't happen
|
||||
*/
|
||||
String registerUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException;
|
||||
|
||||
/**
|
||||
* Standard Authentication flow
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
* @return result of authentication, which might eventually encapsulate info about authenticated user and provider which successfully authenticated
|
||||
*/
|
||||
AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
|
||||
|
||||
|
||||
/**
|
||||
* Update credential
|
||||
*
|
||||
* @param realm
|
||||
* @param configuration
|
||||
* @param username
|
||||
* @param password
|
||||
* @return true if credential has been successfully updated
|
||||
* @throws AuthenticationProviderException
|
||||
*/
|
||||
boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
|
||||
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AuthenticationProviderException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 15L;
|
||||
|
||||
protected AuthenticationProviderException() {
|
||||
}
|
||||
|
||||
public AuthenticationProviderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationProviderException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AuthenticationProviderException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface AuthenticationProviderFactory extends ProviderFactory<AuthenticationProvider> {
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.AuthenticationLinkModel;
|
||||
import org.keycloak.models.AuthenticationProviderModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.util.ProviderLoader;
|
||||
|
||||
/**
|
||||
* Access point to authentication SPI. It finds configured and available {@link AuthenticationProvider} instances for current realm
|
||||
* and then delegates method call to them.
|
||||
*
|
||||
* Example of usage: AuthenticationProviderManager.getManager(realm).validateUser("joe", "password");
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AuthenticationProviderManager {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AuthenticationProviderManager.class);
|
||||
|
||||
private final RealmModel realm;
|
||||
private final Map<String, AuthenticationProvider> delegates;
|
||||
|
||||
public static AuthenticationProviderManager getManager(RealmModel realm, ProviderSession providerSession) {
|
||||
Iterable<AuthenticationProvider> providers = providerSession.getAllProviders(AuthenticationProvider.class);
|
||||
|
||||
Map<String, AuthenticationProvider> providersMap = new HashMap<String, AuthenticationProvider>();
|
||||
for (AuthenticationProvider provider : providers) {
|
||||
providersMap.put(provider.getName(), provider);
|
||||
}
|
||||
|
||||
return new AuthenticationProviderManager(realm, providersMap);
|
||||
}
|
||||
|
||||
public AuthenticationProviderManager(RealmModel realm, Map<String, AuthenticationProvider> delegates) {
|
||||
this.realm = realm;
|
||||
this.delegates = delegates;
|
||||
}
|
||||
|
||||
public AuthUser getUser(String username) {
|
||||
List<AuthenticationProviderModel> authProviderModels = getConfiguredProviderModels(realm);
|
||||
for (AuthenticationProviderModel providerModel : authProviderModels) {
|
||||
AuthenticationProvider delegate = getProvider(providerModel.getProviderName());
|
||||
if (delegate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
AuthUser authUser = delegate.getUser(realm, providerModel.getConfig(), username);
|
||||
if (authUser != null) {
|
||||
logger.debugf("User '%s' found with provider '%s'", username, providerModel.getProviderName());
|
||||
return authUser;
|
||||
}
|
||||
} catch (AuthenticationProviderException ape) {
|
||||
logger.warn(ape.getMessage(), ape);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debugf("User '%s' not found with any provider", username);
|
||||
return null;
|
||||
}
|
||||
|
||||
public AuthProviderStatus validatePassword(UserModel user, String password) {
|
||||
AuthenticationLinkModel authLink = user.getAuthenticationLink();
|
||||
if (authLink == null) {
|
||||
// User not yet linked with any authenticationProvider. Find provider with biggest priority where he is and link
|
||||
AuthUser authUser = getUser(user.getLoginName());
|
||||
authLink = new AuthenticationLinkModel(authUser.getProviderName(), authUser.getId());
|
||||
user.setAuthenticationLink(authLink);
|
||||
logger.infof("User '%s' linked with provider '%s'", authUser.getUsername(), authUser.getProviderName());
|
||||
}
|
||||
|
||||
String providerName = authLink.getAuthProvider();
|
||||
|
||||
AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
|
||||
AuthenticationProvider delegate = getProvider(providerName);
|
||||
if (delegate == null || providerModel == null) {
|
||||
return AuthProviderStatus.FAILED;
|
||||
}
|
||||
|
||||
try {
|
||||
checkCorrectAuthLink(delegate, providerModel, authLink, user.getLoginName());
|
||||
|
||||
AuthProviderStatus currentResult = delegate.validatePassword(realm, providerModel.getConfig(), user.getLoginName(), password);
|
||||
logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.toString(), user.getLoginName());
|
||||
return currentResult;
|
||||
} catch (AuthenticationProviderException ape) {
|
||||
logger.warn(ape.getMessage(), ape);
|
||||
return AuthProviderStatus.FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean updatePassword(UserModel user, String password) throws AuthenticationProviderException {
|
||||
AuthenticationLinkModel authLink = user.getAuthenticationLink();
|
||||
if (authLink == null) {
|
||||
// Find provider with biggest priority where password update is supported. Then register user here and link him
|
||||
List<AuthenticationProviderModel> configuredProviders = getConfiguredProviderModels(realm);
|
||||
for (AuthenticationProviderModel providerModel : configuredProviders) {
|
||||
if (providerModel.isPasswordUpdateSupported()) {
|
||||
AuthenticationProvider delegate = getProvider(providerModel.getProviderName());
|
||||
if (delegate != null) {
|
||||
AuthUser authUser = delegate.getUser(realm, providerModel.getConfig(), user.getLoginName());
|
||||
if (authUser != null) {
|
||||
// Linking existing user supported just for "model" provider. In other cases throw exception
|
||||
if (providerModel.getProviderName().equals(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName())) {
|
||||
authLink = new AuthenticationLinkModel(providerModel.getProviderName(), authUser.getId());
|
||||
user.setAuthenticationLink(authLink);
|
||||
logger.infof("User '%s' linked with provider '%s'", authUser.getUsername(), authUser.getProviderName());
|
||||
} else {
|
||||
throw new AuthenticationProviderException("User " + authUser.getUsername() + " exists in provider "
|
||||
+ authUser.getProviderName() + " but is not linked with model user");
|
||||
}
|
||||
} else {
|
||||
String userIdInProvider = delegate.registerUser(realm, providerModel.getConfig(), user.getLoginName());
|
||||
authLink = new AuthenticationLinkModel(providerModel.getProviderName(), userIdInProvider);
|
||||
user.setAuthenticationLink(authLink);
|
||||
logger.infof("User '%s' registered in provider '%s' and linked", user.getLoginName(), providerModel.getProviderName());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (authLink == null) {
|
||||
logger.warnf("No providers found where password update is supported for user '%s'", user.getLoginName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String providerName = authLink.getAuthProvider();
|
||||
|
||||
AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
|
||||
if (providerModel == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String username = user.getLoginName();
|
||||
|
||||
// Update just those, which support password update
|
||||
if (providerModel.isPasswordUpdateSupported()) {
|
||||
try {
|
||||
AuthenticationProvider delegate = getProvider(providerName);
|
||||
if (delegate == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
checkCorrectAuthLink(delegate, providerModel, authLink, username);
|
||||
|
||||
if (delegate.updateCredential(realm,providerModel.getConfig(), user.getLoginName(), password)) {
|
||||
logger.debugf("Updated password in authentication provider '%s' for user '%s'", providerName, username);
|
||||
return true;
|
||||
} else {
|
||||
logger.warnf("Password not updated in authentication provider '%s' for user '%s'", providerName, username);
|
||||
return false;
|
||||
}
|
||||
} catch (AuthenticationProviderException ape) {
|
||||
// Rethrow it to upper layer
|
||||
logger.warn("Failed to update password: " + ape.getMessage());
|
||||
throw ape;
|
||||
}
|
||||
} else {
|
||||
logger.warnf("Skip password update for authentication provider '%s' for user '%s'", providerName, username);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticationProvider getProvider(String providerName) {
|
||||
AuthenticationProvider delegate = delegates.get(providerName);
|
||||
if (delegate == null) {
|
||||
logger.warnf("Provider '%s' not available on classpath", providerName);
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
|
||||
private static List<AuthenticationProviderModel> getConfiguredProviderModels(RealmModel realm) {
|
||||
List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
|
||||
|
||||
// Use model based authentication of current realm by default
|
||||
if (configuredProviders == null || configuredProviders.isEmpty()) {
|
||||
configuredProviders = Collections.EMPTY_LIST;
|
||||
logger.warnf("No authentication providers found");
|
||||
}
|
||||
|
||||
return configuredProviders;
|
||||
}
|
||||
|
||||
public static AuthenticationProviderModel getConfiguredProviderModel(RealmModel realm, String providerName) {
|
||||
List<AuthenticationProviderModel> providers = getConfiguredProviderModels(realm);
|
||||
for (AuthenticationProviderModel provider : providers) {
|
||||
if (providerName.equals(provider.getProviderName())) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warnf("Provider '%s' not configured in realm", providerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if ID of linked AuthUser is same as expected ID from authenticationLink . It should catch the case when for example user "john" was deleted in LDAP
|
||||
// and then user "john" has been created again, but it's actually different user with different ID
|
||||
private void checkCorrectAuthLink(AuthenticationProvider authProvider, AuthenticationProviderModel providerModel,
|
||||
AuthenticationLinkModel authLinkModel, String username) throws AuthenticationProviderException {
|
||||
AuthUser authUser = authProvider.getUser(realm, providerModel.getConfig(), username);
|
||||
if (authUser == null) {
|
||||
throw new AuthenticationProviderException("User " + username + " not found in authentication provider " + providerModel.getProviderName());
|
||||
}
|
||||
String userExternalId = authUser.getId();
|
||||
if (!userExternalId.equals(authLinkModel.getAuthUserId())) {
|
||||
throw new AuthenticationProviderException("ID did not match! ID from provider: " + userExternalId + ", ID from authentication link: " + authLinkModel.getAuthUserId());
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AuthenticationSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "authentication";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return AuthenticationProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return AuthenticationProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.authentication.AuthenticationSpi
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-authentication-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-authentication-model</artifactId>
|
||||
<name>Keycloak Authentication Model Based</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<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>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authentication-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package org.keycloak.authentication.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.authentication.AuthProviderStatus;
|
||||
import org.keycloak.authentication.AuthUser;
|
||||
import org.keycloak.authentication.AuthenticationProvider;
|
||||
import org.keycloak.authentication.AuthenticationProviderException;
|
||||
|
||||
/**
|
||||
* Authentication provider, which delegates calling of all methods to specified realm
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractModelAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
|
||||
|
||||
@Override
|
||||
public AuthUser getUser(RealmModel currentRealm, Map<String, String> config, String username) throws AuthenticationProviderException {
|
||||
RealmModel realm = getRealm(currentRealm, config);
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||
return user == null ? null : createAuthenticatedUserInstance(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String registerUser(RealmModel currentRealm, Map<String, String> config, String username) throws AuthenticationProviderException {
|
||||
RealmModel realm = getRealm(currentRealm, config);
|
||||
UserModel user = currentRealm.addUser(username);
|
||||
user.setEnabled(true);
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthProviderStatus validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
|
||||
RealmModel realm = getRealm(currentRealm, config);
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||
|
||||
boolean result = realm.validatePassword(user, password);
|
||||
return result ? AuthProviderStatus.SUCCESS : AuthProviderStatus.INVALID_CREDENTIALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
|
||||
RealmModel realm = getRealm(currentRealm, config);
|
||||
|
||||
// Validate password policy
|
||||
String error = realm.getPasswordPolicy().validate(password);
|
||||
if (error != null) {
|
||||
throw new AuthenticationProviderException(error);
|
||||
}
|
||||
|
||||
UserModel user = realm.getUser(username);
|
||||
if (user == null) {
|
||||
logger.warnf("User '%s' doesn't exists. Skip password update", username);
|
||||
return false;
|
||||
}
|
||||
|
||||
UserCredentialModel cred = new UserCredentialModel();
|
||||
cred.setType(CredentialRepresentation.PASSWORD);
|
||||
cred.setValue(password);
|
||||
|
||||
user.updateCredential(cred);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
protected abstract RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) throws AuthenticationProviderException;
|
||||
|
||||
protected AuthUser createAuthenticatedUserInstance(UserModel user) {
|
||||
return new AuthUser(user.getId(), user.getLoginName(), getName())
|
||||
.setName(user.getFirstName(), user.getLastName())
|
||||
.setEmail(user.getEmail());
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.keycloak.authentication.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.authentication.AuthProviderConstants;
|
||||
import org.keycloak.authentication.AuthenticationProviderException;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* AbstractModelAuthenticationProvider, which delegates authentication operations to different (external) realm
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ExternalModelAuthenticationProvider extends AbstractModelAuthenticationProvider {
|
||||
|
||||
public ExternalModelAuthenticationProvider(ProviderSession providerSession) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAvailableOptions() {
|
||||
return Arrays.asList(AuthProviderConstants.EXTERNAL_REALM_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(RealmModel currentRealm, Map<String, String> configuration) throws AuthenticationProviderException {
|
||||
String realmId = configuration.get(AuthProviderConstants.EXTERNAL_REALM_ID);
|
||||
if (realmId == null) {
|
||||
throw new AuthenticationProviderException("Option '" + AuthProviderConstants.EXTERNAL_REALM_ID + "' not specified in configuration");
|
||||
}
|
||||
|
||||
// TODO: This won't be needed when KeycloakSession is available from ProviderSession
|
||||
KeycloakSession session = ResteasyProviderFactory.getContextData(KeycloakSession.class);
|
||||
if (session == null) {
|
||||
throw new AuthenticationProviderException("KeycloakSession not available");
|
||||
}
|
||||
|
||||
RealmModel realm = session.getRealm(realmId);
|
||||
if (realm == null) {
|
||||
throw new AuthenticationProviderException("Realm with id '" + realmId + "' doesn't exists");
|
||||
}
|
||||
return realm;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.keycloak.authentication.model;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.AuthProviderConstants;
|
||||
import org.keycloak.authentication.AuthenticationProvider;
|
||||
import org.keycloak.authentication.AuthenticationProviderFactory;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ExternalModelAuthenticationProviderFactory implements AuthenticationProviderFactory {
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider create(ProviderSession providerSession) {
|
||||
return new ExternalModelAuthenticationProvider(providerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL;
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.keycloak.authentication.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.authentication.AuthProviderConstants;
|
||||
|
||||
/**
|
||||
* AbstractModelAuthenticationProvider, which uses current realm to call operations on
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ModelAuthenticationProvider extends AbstractModelAuthenticationProvider {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return AuthProviderConstants.PROVIDER_NAME_MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAvailableOptions() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) {
|
||||
return currentRealm;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.keycloak.authentication.model;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.AuthProviderConstants;
|
||||
import org.keycloak.authentication.AuthenticationProvider;
|
||||
import org.keycloak.authentication.AuthenticationProviderFactory;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ModelAuthenticationProviderFactory implements AuthenticationProviderFactory {
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider create(ProviderSession providerSession) {
|
||||
return new ModelAuthenticationProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return AuthProviderConstants.PROVIDER_NAME_MODEL;
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
org.keycloak.authentication.model.ModelAuthenticationProviderFactory
|
||||
org.keycloak.authentication.model.ExternalModelAuthenticationProviderFactory
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-authentication-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-authentication-picketlink</artifactId>
|
||||
<name>Keycloak Authentication Picketlink Based</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<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>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authentication-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-picketlink-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
<artifactId>picketlink-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
<artifactId>picketlink-idm-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
<artifactId>picketlink-idm-impl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
<artifactId>picketlink-idm-simple-schema</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package org.keycloak.authentication.picketlink;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authentication.AuthProviderConstants;
|
||||
import org.keycloak.authentication.AuthProviderStatus;
|
||||
import org.keycloak.authentication.AuthUser;
|
||||
import org.keycloak.authentication.AuthenticationProvider;
|
||||
import org.keycloak.authentication.AuthenticationProviderException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.picketlink.IdentityManagerProvider;
|
||||
import org.picketlink.idm.IdentityManagementException;
|
||||
import org.picketlink.idm.IdentityManager;
|
||||
import org.picketlink.idm.credential.Credentials;
|
||||
import org.picketlink.idm.credential.Password;
|
||||
import org.picketlink.idm.credential.UsernamePasswordCredentials;
|
||||
import org.picketlink.idm.model.basic.BasicModel;
|
||||
import org.picketlink.idm.model.basic.User;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AuthenticationProvider, which delegates authentication to picketlink
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PicketlinkAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PicketlinkAuthenticationProvider.class);
|
||||
|
||||
private final IdentityManagerProvider identityManagerProvider;
|
||||
|
||||
public PicketlinkAuthenticationProvider(IdentityManagerProvider identityManagerProvider) {
|
||||
this.identityManagerProvider = identityManagerProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return AuthProviderConstants.PROVIDER_NAME_PICKETLINK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAvailableOptions() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
|
||||
IdentityManager identityManager = getIdentityManager(realm);
|
||||
|
||||
try {
|
||||
User picketlinkUser = BasicModel.getUser(identityManager, username);
|
||||
return picketlinkUser == null ? null : new AuthUser(picketlinkUser.getId(), picketlinkUser.getLoginName(), getName())
|
||||
.setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
|
||||
.setEmail(picketlinkUser.getEmail())
|
||||
.setProviderName(getName());
|
||||
} catch (IdentityManagementException ie) {
|
||||
throw convertIDMException(ie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String registerUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
|
||||
IdentityManager identityManager = getIdentityManager(realm);
|
||||
|
||||
try {
|
||||
User picketlinkUser = new User(username);
|
||||
identityManager.add(picketlinkUser);
|
||||
|
||||
// Hack needed due to ActiveDirectory bug in Picketlink TODO: Remove once https://issues.jboss.org/browse/PLINK-485 fixed and updated in keycloak master
|
||||
picketlinkUser = BasicModel.getUser(identityManager, picketlinkUser.getLoginName());
|
||||
|
||||
return picketlinkUser.getId();
|
||||
} catch (IdentityManagementException ie) {
|
||||
throw convertIDMException(ie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
|
||||
IdentityManager identityManager = getIdentityManager(realm);
|
||||
|
||||
try {
|
||||
UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
|
||||
credential.setUsername(username);
|
||||
credential.setPassword(new Password(password.toCharArray()));
|
||||
identityManager.validateCredentials(credential);
|
||||
if (credential.getStatus() == Credentials.Status.VALID) {
|
||||
return AuthProviderStatus.SUCCESS;
|
||||
} else {
|
||||
return AuthProviderStatus.INVALID_CREDENTIALS;
|
||||
}
|
||||
} catch (IdentityManagementException ie) {
|
||||
throw convertIDMException(ie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
|
||||
IdentityManager identityManager = getIdentityManager(realm);
|
||||
|
||||
try {
|
||||
User picketlinkUser = BasicModel.getUser(identityManager, username);
|
||||
if (picketlinkUser == null) {
|
||||
logger.debugf("User '%s' doesn't exists. Skip password update", username);
|
||||
return false;
|
||||
}
|
||||
|
||||
identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
|
||||
return true;
|
||||
} catch (IdentityManagementException ie) {
|
||||
throw convertIDMException(ie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public IdentityManager getIdentityManager(RealmModel realm) throws AuthenticationProviderException {
|
||||
return identityManagerProvider.getIdentityManager(realm);
|
||||
}
|
||||
|
||||
private AuthenticationProviderException convertIDMException(IdentityManagementException ie) {
|
||||
Throwable realCause = ie;
|
||||
while (realCause.getCause() != null) {
|
||||
realCause = realCause.getCause();
|
||||
}
|
||||
|
||||
// Use the message from the realCause
|
||||
return new AuthenticationProviderException(realCause.getMessage(), ie);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.keycloak.authentication.picketlink;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.AuthProviderConstants;
|
||||
import org.keycloak.authentication.AuthenticationProvider;
|
||||
import org.keycloak.authentication.AuthenticationProviderFactory;
|
||||
import org.keycloak.picketlink.IdentityManagerProvider;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PicketlinkAuthenticationProviderFactory implements AuthenticationProviderFactory {
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider create(ProviderSession providerSession) {
|
||||
return new PicketlinkAuthenticationProvider(providerSession.getProvider(IdentityManagerProvider.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return AuthProviderConstants.PROVIDER_NAME_PICKETLINK;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.authentication.picketlink.PicketlinkAuthenticationProviderFactory
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-beta-3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<artifactId>keycloak-authentication-parent</artifactId>
|
||||
<name>Keycloak Authentication</name>
|
||||
<description />
|
||||
|
||||
<modules>
|
||||
<module>authentication-api</module>
|
||||
<module>authentication-model</module>
|
||||
<module>authentication-picketlink</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<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.7.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-broker-core</artifactId>
|
||||
<name>Keycloak Broker Core</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> implements IdentityProvider<C> {
|
||||
|
||||
private final C config;
|
||||
|
||||
public AbstractIdentityProvider(C config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public C getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response performLogin(AuthenticationRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderDataMarshaller getMarshaller() {
|
||||
return new DefaultDataMarshaller();
|
||||
}
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public abstract class AbstractIdentityProviderFactory<T extends IdentityProvider> implements IdentityProviderFactory<T> {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public T create(KeycloakSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderMapper create(KeycloakSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(org.keycloak.Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class AuthenticationRequest {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final UriInfo uriInfo;
|
||||
private final String state;
|
||||
private final HttpRequest httpRequest;
|
||||
private final RealmModel realm;
|
||||
private final String redirectUri;
|
||||
private final ClientSessionModel clientSession;
|
||||
|
||||
public AuthenticationRequest(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.httpRequest = httpRequest;
|
||||
this.uriInfo = uriInfo;
|
||||
this.state = state;
|
||||
this.redirectUri = redirectUri;
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public UriInfo getUriInfo() {
|
||||
return this.uriInfo;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public HttpRequest getHttpRequest() {
|
||||
return this.httpRequest;
|
||||
}
|
||||
|
||||
public RealmModel getRealm() {
|
||||
return this.realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the redirect url that must be included in an authentication request in order to process responses from an
|
||||
* identity provider.</p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getRedirectUri() {
|
||||
return this.redirectUri;
|
||||
}
|
||||
|
||||
public ClientSessionModel getClientSession() {
|
||||
return this.clientSession;
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Represents all identity information obtained from an {@link org.keycloak.broker.provider.IdentityProvider} after a
|
||||
* successful authentication.</p>
|
||||
*
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class BrokeredIdentityContext {
|
||||
|
||||
private String id;
|
||||
private String username;
|
||||
private String modelUsername;
|
||||
private String email;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String brokerSessionId;
|
||||
private String brokerUserId;
|
||||
private String code;
|
||||
private String token;
|
||||
private IdentityProviderModel idpConfig;
|
||||
private IdentityProvider idp;
|
||||
private Map<String, Object> contextData = new HashMap<>();
|
||||
private ClientSessionModel clientSession;
|
||||
|
||||
public BrokeredIdentityContext(String id) {
|
||||
if (id == null) {
|
||||
throw new RuntimeException("No identifier provider for identity.");
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Username in remote idp
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* username to store in UserModel
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getModelUsername() {
|
||||
return modelUsername;
|
||||
}
|
||||
|
||||
public void setModelUsername(String modelUsername) {
|
||||
this.modelUsername = modelUsername;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getBrokerSessionId() {
|
||||
return brokerSessionId;
|
||||
}
|
||||
|
||||
public void setBrokerSessionId(String brokerSessionId) {
|
||||
this.brokerSessionId = brokerSessionId;
|
||||
}
|
||||
|
||||
public String getBrokerUserId() {
|
||||
return brokerUserId;
|
||||
}
|
||||
|
||||
public void setBrokerUserId(String brokerUserId) {
|
||||
this.brokerUserId = brokerUserId;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public IdentityProviderModel getIdpConfig() {
|
||||
return idpConfig;
|
||||
}
|
||||
|
||||
public void setIdpConfig(IdentityProviderModel idpConfig) {
|
||||
this.idpConfig = idpConfig;
|
||||
}
|
||||
|
||||
public IdentityProvider getIdp() {
|
||||
return idp;
|
||||
}
|
||||
|
||||
public void setIdp(IdentityProvider idp) {
|
||||
this.idp = idp;
|
||||
}
|
||||
|
||||
public Map<String, Object> getContextData() {
|
||||
return contextData;
|
||||
}
|
||||
|
||||
public void setContextData(Map<String, Object> contextData) {
|
||||
this.contextData = contextData;
|
||||
}
|
||||
|
||||
// Set the attribute, which will be available on "Update profile" page and in authenticators
|
||||
public void setUserAttribute(String attributeName, String attributeValue) {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add(attributeValue);
|
||||
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list);
|
||||
}
|
||||
|
||||
public String getUserAttribute(String attributeName) {
|
||||
List<String> userAttribute = (List<String>) getContextData().get(Constants.USER_ATTRIBUTES_PREFIX + attributeName);
|
||||
if (userAttribute == null || userAttribute.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return userAttribute.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public ClientSessionModel getClientSession() {
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
public void setClientSession(ClientSessionModel clientSession) {
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
if (name != null) {
|
||||
int i = name.lastIndexOf(' ');
|
||||
if (i != -1) {
|
||||
firstName = name.substring(0, i);
|
||||
lastName = name.substring(i + 1);
|
||||
} else {
|
||||
firstName = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"id='" + id + '\'' +
|
||||
", username='" + username + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class DefaultDataMarshaller implements IdentityProviderDataMarshaller {
|
||||
|
||||
@Override
|
||||
public String serialize(Object value) {
|
||||
if (value instanceof String) {
|
||||
return (String) value;
|
||||
} else {
|
||||
try {
|
||||
byte[] bytes = JsonSerialization.writeValueAsBytes(value);
|
||||
return Base64Url.encode(bytes);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T deserialize(String serialized, Class<T> clazz) {
|
||||
try {
|
||||
if (clazz.equals(String.class)) {
|
||||
return clazz.cast(serialized);
|
||||
} else {
|
||||
byte[] bytes = Base64Url.decode(serialized);
|
||||
if (List.class.isAssignableFrom(clazz)) {
|
||||
List list = JsonSerialization.readValue(bytes, List.class);
|
||||
return clazz.cast(list);
|
||||
} else {
|
||||
return JsonSerialization.readValue(bytes, clazz);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
|
||||
public static final String ATTRIBUTE = "attribute";
|
||||
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ATTRIBUTE);
|
||||
property.setLabel("User Attribute");
|
||||
property.setHelpText("Name of user attribute you want to hardcode");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ATTRIBUTE_VALUE);
|
||||
property.setLabel("User Attribute Value");
|
||||
property.setHelpText("Value you want to hardcode");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Attribute Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Hardcoded Attribute";
|
||||
}
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
||||
|
||||
|
||||
public static final String PROVIDER_ID = "hardcoded-attribute-idp-mapper";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||
context.setUserAttribute(attribute, attributeValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||
user.setSingleAttribute(attribute, attributeValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "When user is imported from provider, hardcode a value to a specific user attribute.";
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
||||
public static final String ROLE = "role";
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ROLE);
|
||||
property.setLabel("Role");
|
||||
property.setHelpText("Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String[] parseRole(String role) {
|
||||
int scopeIndex = role.lastIndexOf('.');
|
||||
if (scopeIndex > -1) {
|
||||
String appName = role.substring(0, scopeIndex);
|
||||
role = role.substring(scopeIndex + 1);
|
||||
String[] rtn = {appName, role};
|
||||
return rtn;
|
||||
} else {
|
||||
String[] rtn = {null, role};
|
||||
return rtn;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
|
||||
String[] parsedRole = parseRole(roleName);
|
||||
RoleModel role = null;
|
||||
if (parsedRole[0] == null) {
|
||||
role = realm.getRole(parsedRole[1]);
|
||||
} else {
|
||||
ClientModel client = realm.getClientByClientId(parsedRole[0]);
|
||||
role = client.getRole(parsedRole[1]);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Role Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Hardcoded Role";
|
||||
}
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
||||
|
||||
|
||||
public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(ROLE);
|
||||
RoleModel role = getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.grantRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "When user is imported from provider, hardcode a role mapping for it.";
|
||||
}
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProviderMapper {
|
||||
public static final String ATTRIBUTE = "attribute";
|
||||
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ATTRIBUTE);
|
||||
property.setLabel("User Session Attribute");
|
||||
property.setHelpText("Name of user session attribute you want to hardcode");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(ATTRIBUTE_VALUE);
|
||||
property.setLabel("User Session Attribute Value");
|
||||
property.setHelpText("Value you want to hardcode");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Attribute Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Hardcoded User Session Attribute";
|
||||
}
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
||||
|
||||
|
||||
public static final String PROVIDER_ID = "hardcoded-user-session-attribute-idp-mapper";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "When user is imported from provider, hardcode a value to a specific user session attribute.";
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
/**
|
||||
* @author pedroigor
|
||||
*/
|
||||
public class IdentityBrokerException extends RuntimeException {
|
||||
|
||||
public IdentityBrokerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IdentityBrokerException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
|
||||
|
||||
interface AuthenticationCallback {
|
||||
/**
|
||||
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
|
||||
* with the remote IDP
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
Response authenticated(BrokeredIdentityContext context);
|
||||
|
||||
Response cancelled(String code);
|
||||
|
||||
Response error(String code, String message);
|
||||
}
|
||||
|
||||
|
||||
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
|
||||
void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context);
|
||||
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
|
||||
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
|
||||
|
||||
/**
|
||||
* JAXRS callback endpoint for when the remote IDP wants to callback to keycloak.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event);
|
||||
|
||||
/**
|
||||
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
|
||||
* only once during the authentication.</p>
|
||||
*
|
||||
* @param request The initial authentication request. Contains all the contextual information in order to build an authentication request to the
|
||||
* identity provider.
|
||||
* @return
|
||||
*/
|
||||
Response performLogin(AuthenticationRequest request);
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link javax.ws.rs.core.Response} containing the token previously stored during the authentication process for a
|
||||
* specific user.</p>
|
||||
*
|
||||
* @param identity
|
||||
* @return
|
||||
*/
|
||||
Response retrieveToken(FederatedIdentityModel identity);
|
||||
|
||||
void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
|
||||
|
||||
/**
|
||||
* Called when a Keycloak application initiates a logout through the browser. This is expected to do a logout
|
||||
* with the IDP
|
||||
*
|
||||
* @param userSession
|
||||
* @param uriInfo
|
||||
* @param realm
|
||||
* @return null if this is not supported by this provider
|
||||
*/
|
||||
Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
|
||||
|
||||
/**
|
||||
* Export a representation of the IdentityProvider in a specific format. For example, a SAML EntityDescriptor
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Response export(UriInfo uriInfo, RealmModel realm, String format);
|
||||
|
||||
/**
|
||||
* Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
|
||||
* @return
|
||||
*/
|
||||
IdentityProviderDataMarshaller getMarshaller();
|
||||
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface IdentityProviderDataMarshaller {
|
||||
|
||||
String serialize(Object obj);
|
||||
<T> T deserialize(String serialized, Class<T> clazz);
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public interface IdentityProviderFactory<T extends IdentityProvider> extends ProviderFactory<T> {
|
||||
|
||||
/**
|
||||
* <p>A friendly name for this factory.</p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* <p>Creates an {@link IdentityProvider} based on the configuration contained in
|
||||
* <code>model</code>.</p>
|
||||
*
|
||||
* @param model The configuration to be used to create the identity provider.
|
||||
* @return
|
||||
*/
|
||||
T create(IdentityProviderModel model);
|
||||
|
||||
/**
|
||||
* <p>Creates an {@link IdentityProvider} based on the configuration from
|
||||
* <code>inputStream</code>.</p>
|
||||
*
|
||||
* @param model The model containing the common abd basic configuration for an identity provider.
|
||||
* @param inputStream The input stream from where configuration will be loaded from..
|
||||
* @return
|
||||
*/
|
||||
Map<String, String> parseConfig(InputStream inputStream);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ConfiguredProvider;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider {
|
||||
public static final String ANY_PROVIDER = "*";
|
||||
|
||||
String[] getCompatibleProviders();
|
||||
String getDisplayCategory();
|
||||
String getDisplayType();
|
||||
|
||||
/**
|
||||
* Called to determine what keycloak username and email to use to process the login request from the external IDP.
|
||||
* It's called before "FirstBrokerLogin" flow, so can be used to map attributes to BrokeredIdentityContext ( BrokeredIdentityContext.setUserAttribute ),
|
||||
* which will be available on "Review Profile" page and in authenticators during FirstBrokerLogin flow
|
||||
*
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param mapperModel
|
||||
* @param context
|
||||
*/
|
||||
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||
|
||||
/**
|
||||
* Called after UserModel is created for first time for this user. Called after "FirstBrokerLogin" flow
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param user
|
||||
* @param mapperModel
|
||||
* @param context
|
||||
*/
|
||||
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||
|
||||
/**
|
||||
* Called when this user has logged in before and has already been imported.
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param user
|
||||
* @param mapperModel
|
||||
* @param context
|
||||
*/
|
||||
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class IdentityProviderMapperSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "identity-provider-mapper";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return IdentityProviderMapper.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return IdentityProviderMapper.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class IdentityProviderSpi implements Spi {
|
||||
|
||||
public static final String IDENTITY_PROVIDER_SPI_NAME = "identity_provider";
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return IDENTITY_PROVIDER_SPI_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return IdentityProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return IdentityProviderFactory.class;
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
package org.keycloak.broker.provider.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @author Vlastimil Elias (velias at redhat dot com)
|
||||
*/
|
||||
public class SimpleHttp {
|
||||
|
||||
|
||||
private String url;
|
||||
private String method;
|
||||
private Map<String, String> headers;
|
||||
private Map<String, String> params;
|
||||
|
||||
protected SimpleHttp(String url, String method) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public static SimpleHttp doGet(String url) {
|
||||
return new SimpleHttp(url, "GET");
|
||||
}
|
||||
|
||||
public static SimpleHttp doPost(String url) {
|
||||
return new SimpleHttp(url, "POST");
|
||||
}
|
||||
|
||||
public SimpleHttp header(String name, String value) {
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleHttp param(String name, String value) {
|
||||
if (params == null) {
|
||||
params = new HashMap<String, String>();
|
||||
}
|
||||
params.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public String asString() throws IOException {
|
||||
boolean get = method.equals("GET");
|
||||
boolean post = method.equals("POST");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (get) {
|
||||
sb.append(url);
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
boolean f = true;
|
||||
for (Map.Entry<String, String> p : params.entrySet()) {
|
||||
if (f) {
|
||||
f = false;
|
||||
if (get) {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(URLEncoder.encode(p.getKey(), "UTF-8"));
|
||||
sb.append("=");
|
||||
sb.append(URLEncoder.encode(p.getValue(), "UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
if (get) {
|
||||
url = sb.toString();
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
OutputStream os = null;
|
||||
InputStream is = null;
|
||||
|
||||
try {
|
||||
connection.setRequestMethod(method);
|
||||
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> h : headers.entrySet()) {
|
||||
connection.setRequestProperty(h.getKey(), h.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (post) {
|
||||
String data = sb.toString();
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
connection.setRequestProperty("Content-Length", String.valueOf(data.length()));
|
||||
|
||||
os = connection.getOutputStream();
|
||||
os.write(data.getBytes());
|
||||
} else {
|
||||
connection.setDoOutput(false);
|
||||
}
|
||||
|
||||
String ce = connection.getHeaderField("Content-Encoding");
|
||||
is = connection.getInputStream();
|
||||
if ("gzip".equals(ce)) {
|
||||
is = new GZIPInputStream(is);
|
||||
}
|
||||
return toString(is);
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int asStatus() throws IOException {
|
||||
boolean get = method.equals("GET");
|
||||
boolean post = method.equals("POST");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (get) {
|
||||
sb.append(url);
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
boolean f = true;
|
||||
for (Map.Entry<String, String> p : params.entrySet()) {
|
||||
if (f) {
|
||||
f = false;
|
||||
if (get) {
|
||||
sb.append("?");
|
||||
}
|
||||
} else {
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(URLEncoder.encode(p.getKey(), "UTF-8"));
|
||||
sb.append("=");
|
||||
sb.append(URLEncoder.encode(p.getValue(), "UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
if (get) {
|
||||
url = sb.toString();
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
OutputStream os = null;
|
||||
InputStream is = null;
|
||||
|
||||
try {
|
||||
connection.setRequestMethod(method);
|
||||
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> h : headers.entrySet()) {
|
||||
connection.setRequestProperty(h.getKey(), h.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (post) {
|
||||
String data = sb.toString();
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
connection.setRequestProperty("Content-Length", String.valueOf(data.length()));
|
||||
|
||||
os = connection.getOutputStream();
|
||||
os.write(data.getBytes());
|
||||
} else {
|
||||
connection.setDoOutput(false);
|
||||
}
|
||||
|
||||
is = connection.getInputStream();
|
||||
return connection.getResponseCode();
|
||||
} finally {
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(InputStream is) throws IOException {
|
||||
InputStreamReader reader = new InputStreamReader(is);
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
char[] buffer = new char[1024 * 4];
|
||||
for (int n = reader.read(buffer); n != -1; n = reader.read(buffer)) {
|
||||
writer.write(buffer, 0, n);
|
||||
}
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
org.keycloak.broker.provider.HardcodedRoleMapper
|
||||
org.keycloak.broker.provider.HardcodedAttributeMapper
|
||||
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
|
||||
@@ -1,2 +0,0 @@
|
||||
org.keycloak.broker.provider.IdentityProviderSpi
|
||||
org.keycloak.broker.provider.IdentityProviderMapperSpi
|
||||
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<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.7.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-broker-oidc</artifactId>
|
||||
<name>Keycloak Broker - OpenID Connect Identity Provider</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-broker-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> {
|
||||
protected static final Logger logger = Logger.getLogger(AbstractOAuth2IdentityProvider.class);
|
||||
|
||||
public static final String OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
|
||||
public static final String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
|
||||
public static final String FEDERATED_REFRESH_TOKEN = "FEDERATED_REFRESH_TOKEN";
|
||||
public static final String FEDERATED_TOKEN_EXPIRATION = "FEDERATED_TOKEN_EXPIRATION";
|
||||
public static final String ACCESS_DENIED = "access_denied";
|
||||
protected static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static final String OAUTH2_PARAMETER_ACCESS_TOKEN = "access_token";
|
||||
public static final String OAUTH2_PARAMETER_SCOPE = "scope";
|
||||
public static final String OAUTH2_PARAMETER_STATE = "state";
|
||||
public static final String OAUTH2_PARAMETER_RESPONSE_TYPE = "response_type";
|
||||
public static final String OAUTH2_PARAMETER_REDIRECT_URI = "redirect_uri";
|
||||
public static final String OAUTH2_PARAMETER_CODE = "code";
|
||||
public static final String OAUTH2_PARAMETER_CLIENT_ID = "client_id";
|
||||
public static final String OAUTH2_PARAMETER_CLIENT_SECRET = "client_secret";
|
||||
public static final String OAUTH2_PARAMETER_GRANT_TYPE = "grant_type";
|
||||
|
||||
|
||||
public AbstractOAuth2IdentityProvider(C config) {
|
||||
super(config);
|
||||
|
||||
if (config.getDefaultScope() == null || config.getDefaultScope().isEmpty()) {
|
||||
config.setDefaultScope(getDefaultScopes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new Endpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response performLogin(AuthenticationRequest request) {
|
||||
try {
|
||||
URI authorizationUrl = createAuthorizationUrl(request).build();
|
||||
|
||||
return Response.temporaryRedirect(authorizationUrl).build();
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response retrieveToken(FederatedIdentityModel identity) {
|
||||
return Response.ok(identity.getToken()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public C getConfig() {
|
||||
return super.getConfig();
|
||||
}
|
||||
|
||||
protected String extractTokenFromResponse(String response, String tokenName) {
|
||||
if(response == null)
|
||||
return null;
|
||||
|
||||
if (response.startsWith("{")) {
|
||||
try {
|
||||
JsonNode node = mapper.readTree(response);
|
||||
if(node.has(tokenName)){
|
||||
String s = node.get(tokenName).getTextValue();
|
||||
if(s == null || s.trim().isEmpty())
|
||||
return null;
|
||||
return s;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IdentityBrokerException("Could not extract token [" + tokenName + "] from response [" + response + "] due: " + e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected BrokeredIdentityContext getFederatedIdentity(String response) {
|
||||
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new IdentityBrokerException("No access token available in OAuth server response: " + response);
|
||||
}
|
||||
|
||||
return doGetFederatedIdentity(accessToken);
|
||||
}
|
||||
|
||||
|
||||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
|
||||
return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
|
||||
.queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
|
||||
.queryParam(OAUTH2_PARAMETER_STATE, request.getState())
|
||||
.queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
|
||||
.queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||
.queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON property as text. JSON numbers and booleans are converted to text. Empty string is converted to null.
|
||||
*
|
||||
* @param jsonNode to get property from
|
||||
* @param name of property to get
|
||||
* @return string value of the property or null.
|
||||
*/
|
||||
protected String getJsonProperty(JsonNode jsonNode, String name) {
|
||||
if (jsonNode.has(name) && !jsonNode.get(name).isNull()) {
|
||||
String s = jsonNode.get(name).asText();
|
||||
if(s != null && !s.isEmpty())
|
||||
return s;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected JsonNode asJsonNode(String json) throws IOException {
|
||||
return mapper.readTree(json);
|
||||
}
|
||||
|
||||
protected abstract String getDefaultScopes();
|
||||
|
||||
protected class Endpoint {
|
||||
protected AuthenticationCallback callback;
|
||||
protected RealmModel realm;
|
||||
protected EventBuilder event;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
protected HttpHeaders headers;
|
||||
|
||||
@Context
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
public Endpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
this.callback = callback;
|
||||
this.realm = realm;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@GET
|
||||
public Response authResponse(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_STATE) String state,
|
||||
@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE) String authorizationCode,
|
||||
@QueryParam(OAuth2Constants.ERROR) String error) {
|
||||
if (error != null) {
|
||||
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
|
||||
if (error.equals(ACCESS_DENIED)) {
|
||||
return callback.cancelled(state);
|
||||
} else {
|
||||
return callback.error(state, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (authorizationCode != null) {
|
||||
String response = generateTokenRequest(authorizationCode).asString();
|
||||
|
||||
BrokeredIdentityContext federatedIdentity = getFederatedIdentity(response);
|
||||
|
||||
if (getConfig().isStoreToken()) {
|
||||
federatedIdentity.setToken(response);
|
||||
}
|
||||
|
||||
federatedIdentity.setCode(state);
|
||||
federatedIdentity.setIdpConfig(getConfig());
|
||||
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
|
||||
|
||||
return callback.authenticated(federatedIdentity);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to make identity provider oauth callback", e);
|
||||
}
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
|
||||
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
|
||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||
return SimpleHttp.doPost(getConfig().getTokenUrl())
|
||||
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
||||
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
||||
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||
|
||||
public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN";
|
||||
|
||||
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new KeycloakEndpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
|
||||
JsonWebToken access = validateToken(idpKey, response.getToken());
|
||||
context.getContextData().put(VALIDATED_ACCESS_TOKEN, access);
|
||||
}
|
||||
|
||||
protected class KeycloakEndpoint extends OIDCEndpoint {
|
||||
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
super(callback, realm, event);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path(AdapterConstants.K_LOGOUT)
|
||||
public Response backchannelLogout(String input) {
|
||||
JWSInput token = null;
|
||||
try {
|
||||
token = new JWSInput(input);
|
||||
} catch (JWSInputException e) {
|
||||
logger.warn("Failed to verify logout request");
|
||||
return Response.status(400).build();
|
||||
}
|
||||
PublicKey key = getExternalIdpKey();
|
||||
if (key != null) {
|
||||
if (!verify(token, key)) {
|
||||
logger.warn("Failed to verify logout request");
|
||||
return Response.status(400).build();
|
||||
}
|
||||
}
|
||||
LogoutAction action = null;
|
||||
try {
|
||||
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!validateAction(action)) return Response.status(400).build();
|
||||
if (action.getKeycloakSessionIds() != null) {
|
||||
for (String sessionId : action.getKeycloakSessionIds()) {
|
||||
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
|
||||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||
if (userSession != null
|
||||
&& userSession.getState() != UserSessionModel.State.LOGGING_OUT
|
||||
&& userSession.getState() != UserSessionModel.State.LOGGED_OUT
|
||||
) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
protected boolean validateAction(AdminAction action) {
|
||||
if (!action.validate()) {
|
||||
logger.warn("admin request failed, not validated" + action.getAction());
|
||||
return false;
|
||||
}
|
||||
if (action.isExpired()) {
|
||||
logger.warn("admin request failed, expired token");
|
||||
return false;
|
||||
}
|
||||
if (!getConfig().getClientId().equals(action.getResource())) {
|
||||
logger.warn("Resource name does not match");
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||
return super.generateTokenRequest(authorizationCode)
|
||||
.param(AdapterConstants.CLIENT_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.oidc.mappers.UsernameTemplateMapper;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<KeycloakOIDCIdentityProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "keycloak-oidc";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Keycloak OpenID Connect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakOIDCIdentityProvider create(IdentityProviderModel model) {
|
||||
return new KeycloakOIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
|
||||
|
||||
public OAuth2IdentityProviderConfig(IdentityProviderModel model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
public String getAuthorizationUrl() {
|
||||
return getConfig().get("authorizationUrl");
|
||||
}
|
||||
|
||||
public void setAuthorizationUrl(String authorizationUrl) {
|
||||
getConfig().put("authorizationUrl", authorizationUrl);
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return getConfig().get("tokenUrl");
|
||||
}
|
||||
|
||||
public void setTokenUrl(String tokenUrl) {
|
||||
getConfig().put("tokenUrl", tokenUrl);
|
||||
}
|
||||
|
||||
public String getUserInfoUrl() {
|
||||
return getConfig().get("userInfoUrl");
|
||||
}
|
||||
|
||||
public void setUserInfoUrl(String userInfoUrl) {
|
||||
getConfig().put("userInfoUrl", userInfoUrl);
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return getConfig().get("clientId");
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
getConfig().put("clientId", clientId);
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return getConfig().get("clientSecret");
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
getConfig().put("clientSecret", clientSecret);
|
||||
}
|
||||
|
||||
public String getDefaultScope() {
|
||||
return getConfig().get("defaultScope");
|
||||
}
|
||||
|
||||
public void setDefaultScope(String defaultScope) {
|
||||
getConfig().put("defaultScope", defaultScope);
|
||||
}
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
|
||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> {
|
||||
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
|
||||
|
||||
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
|
||||
public static final String SCOPE_OPENID = "openid";
|
||||
public static final String FEDERATED_ID_TOKEN = "FEDERATED_ID_TOKEN";
|
||||
public static final String USER_INFO = "UserInfo";
|
||||
public static final String FEDERATED_ACCESS_TOKEN_RESPONSE = "FEDERATED_ACCESS_TOKEN_RESPONSE";
|
||||
public static final String VALIDATED_ID_TOKEN = "VALIDATED_ID_TOKEN";
|
||||
|
||||
public OIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||
super(config);
|
||||
|
||||
String defaultScope = config.getDefaultScope();
|
||||
|
||||
if (!defaultScope.contains(SCOPE_OPENID)) {
|
||||
config.setDefaultScope(SCOPE_OPENID + " " + defaultScope);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new OIDCEndpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
protected PublicKey getExternalIdpKey() {
|
||||
String signingCert = getConfig().getCertificateSignatureVerifier();
|
||||
try {
|
||||
if (signingCert != null && !signingCert.trim().equals("")) {
|
||||
return PemUtils.decodeCertificate(signingCert).getPublicKey();
|
||||
} else if (getConfig().getPublicKeySignatureVerifier() != null && !getConfig().getPublicKeySignatureVerifier().trim().equals("")) {
|
||||
return PemUtils.decodePublicKey(getConfig().getPublicKeySignatureVerifier());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
protected class OIDCEndpoint extends Endpoint {
|
||||
public OIDCEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
super(callback, realm, event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GET
|
||||
@Path("logout_response")
|
||||
public Response logoutResponse(@Context UriInfo uriInfo,
|
||||
@QueryParam("state") String state) {
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, state);
|
||||
if (userSession == null) {
|
||||
logger.error("no valid user session");
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
||||
logger.error("usersession in different state");
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
|
||||
}
|
||||
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backchannelLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||
String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
|
||||
if (idToken == null) return;
|
||||
backchannelLogout(userSession, idToken);
|
||||
}
|
||||
|
||||
protected void backchannelLogout(UserSessionModel userSession, String idToken) {
|
||||
String sessionId = userSession.getId();
|
||||
UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
|
||||
.queryParam("state", sessionId);
|
||||
logoutUri.queryParam("id_token_hint", idToken);
|
||||
String url = logoutUri.build().toString();
|
||||
try {
|
||||
int status = JsonSimpleHttp.doGet(url).asStatus();
|
||||
boolean success = status >=200 && status < 400;
|
||||
if (!success) {
|
||||
logger.warn("Failed backchannel broker logout to: " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed backchannel broker logout to: " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
|
||||
String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
|
||||
if (idToken != null && getConfig().isBackchannelSupported()) {
|
||||
backchannelLogout(userSession, idToken);
|
||||
return null;
|
||||
} else {
|
||||
String sessionId = userSession.getId();
|
||||
UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
|
||||
.queryParam("state", sessionId);
|
||||
if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
|
||||
String redirect = RealmsResource.brokerUrl(uriInfo)
|
||||
.path(IdentityBrokerService.class, "getEndpoint")
|
||||
.path(OIDCEndpoint.class, "logoutResponse")
|
||||
.build(realm.getName(), getConfig().getAlias()).toString();
|
||||
logoutUri.queryParam("post_logout_redirect_uri", redirect);
|
||||
Response response = Response.status(302).location(logoutUri.build()).build();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
|
||||
UriBuilder authorizationUrl = super.createAuthorizationUrl(request);
|
||||
String prompt = getConfig().getPrompt();
|
||||
|
||||
if (prompt != null && !prompt.isEmpty()) {
|
||||
authorizationUrl.queryParam(OAUTH2_PARAMETER_PROMPT, prompt);
|
||||
}
|
||||
|
||||
return authorizationUrl;
|
||||
}
|
||||
|
||||
protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BrokeredIdentityContext getFederatedIdentity(String response) {
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try {
|
||||
tokenResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new IdentityBrokerException("Could not decode access token response.", e);
|
||||
}
|
||||
PublicKey key = getExternalIdpKey();
|
||||
String accessToken = verifyAccessToken(key, tokenResponse);
|
||||
|
||||
String encodedIdToken = tokenResponse.getIdToken();
|
||||
|
||||
|
||||
|
||||
JsonWebToken idToken = validateToken(key, encodedIdToken);
|
||||
|
||||
try {
|
||||
String id = idToken.getSubject();
|
||||
BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
|
||||
String name = (String)idToken.getOtherClaims().get(IDToken.NAME);
|
||||
String preferredUsername = (String)idToken.getOtherClaims().get(IDToken.PREFERRED_USERNAME);
|
||||
String email = (String)idToken.getOtherClaims().get(IDToken.EMAIL);
|
||||
|
||||
if (getConfig().getUserInfoUrl() != null && (id == null || name == null || preferredUsername == null || email == null) ) {
|
||||
SimpleHttp request = JsonSimpleHttp.doGet(getConfig().getUserInfoUrl())
|
||||
.header("Authorization", "Bearer " + accessToken);
|
||||
JsonNode userInfo = JsonSimpleHttp.asJson(request);
|
||||
|
||||
id = getJsonProperty(userInfo, "sub");
|
||||
name = getJsonProperty(userInfo, "name");
|
||||
preferredUsername = getJsonProperty(userInfo, "preferred_username");
|
||||
email = getJsonProperty(userInfo, "email");
|
||||
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
|
||||
}
|
||||
identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
|
||||
identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
|
||||
processAccessTokenResponse(identity, key, tokenResponse);
|
||||
|
||||
identity.setId(id);
|
||||
identity.setName(name);
|
||||
identity.setEmail(email);
|
||||
|
||||
identity.setBrokerUserId(getConfig().getAlias() + "." + id);
|
||||
if (tokenResponse.getSessionState() != null) {
|
||||
identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
|
||||
}
|
||||
|
||||
if (preferredUsername == null) {
|
||||
preferredUsername = email;
|
||||
}
|
||||
|
||||
if (preferredUsername == null) {
|
||||
preferredUsername = id;
|
||||
}
|
||||
|
||||
identity.setUsername(preferredUsername);
|
||||
|
||||
if (getConfig().isStoreToken()) {
|
||||
identity.setToken(response);
|
||||
}
|
||||
|
||||
return identity;
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not fetch attributes from userinfo endpoint.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String verifyAccessToken(PublicKey key, AccessTokenResponse tokenResponse) {
|
||||
String accessToken = tokenResponse.getToken();
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new IdentityBrokerException("No access_token from server.");
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
protected boolean verify(JWSInput jws, PublicKey key) {
|
||||
if (key == null) return true;
|
||||
if (!getConfig().isValidateSignature()) return true;
|
||||
return RSAProvider.verify(jws, key);
|
||||
|
||||
}
|
||||
|
||||
protected JsonWebToken validateToken(PublicKey key, String encodedToken) {
|
||||
if (encodedToken == null) {
|
||||
throw new IdentityBrokerException("No token from server.");
|
||||
}
|
||||
|
||||
JsonWebToken token;
|
||||
try {
|
||||
JWSInput jws = new JWSInput(encodedToken);
|
||||
if (!verify(jws, key)) {
|
||||
throw new IdentityBrokerException("token signature validation failed");
|
||||
}
|
||||
token = jws.readJsonContent(JsonWebToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new IdentityBrokerException("Invalid token", e);
|
||||
}
|
||||
|
||||
String iss = token.getIssuer();
|
||||
|
||||
if (!token.hasAudience(getConfig().getClientId())) {
|
||||
throw new IdentityBrokerException("Wrong audience from token.");
|
||||
}
|
||||
|
||||
if (!token.isActive()) {
|
||||
throw new IdentityBrokerException("Token is no longer valid");
|
||||
}
|
||||
|
||||
String trustedIssuers = getConfig().getIssuer();
|
||||
|
||||
if (trustedIssuers != null) {
|
||||
String[] issuers = trustedIssuers.split(",");
|
||||
|
||||
for (String trustedIssuer : issuers) {
|
||||
if (iss != null && iss.equals(trustedIssuer.trim())) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
|
||||
AccessTokenResponse tokenResponse = (AccessTokenResponse)context.getContextData().get(FEDERATED_ACCESS_TOKEN_RESPONSE);
|
||||
userSession.setNote(FEDERATED_ACCESS_TOKEN, tokenResponse.getToken());
|
||||
userSession.setNote(FEDERATED_ID_TOKEN, tokenResponse.getIdToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultScopes() {
|
||||
return "openid";
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
||||
|
||||
public OIDCIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
|
||||
super(identityProviderModel);
|
||||
}
|
||||
|
||||
public String getPrompt() {
|
||||
return getConfig().get("prompt");
|
||||
}
|
||||
public void setPrompt(String prompt) {
|
||||
getConfig().put("prompt", prompt);
|
||||
}
|
||||
|
||||
public String getIssuer() {
|
||||
return getConfig().get("issuer");
|
||||
}
|
||||
public void setIssuer(String issuer) {
|
||||
getConfig().put("issuer", issuer);
|
||||
}
|
||||
public String getLogoutUrl() {
|
||||
return getConfig().get("logoutUrl");
|
||||
}
|
||||
public void setLogoutUrl(String url) {
|
||||
getConfig().put("logoutUrl", url);
|
||||
}
|
||||
public String getCertificateSignatureVerifier() {
|
||||
return getConfig().get("certificateSignatureVerifier");
|
||||
}
|
||||
|
||||
public void setCertificateSignatureVerifier(String signingCertificate) {
|
||||
getConfig().put("certificateSignatureVerifier", signingCertificate);
|
||||
}
|
||||
public String getPublicKeySignatureVerifier() {
|
||||
return getConfig().get("publicKeySignatureVerifier");
|
||||
}
|
||||
|
||||
public void setPublicKeySignatureVerifier(String signingCertificate) {
|
||||
getConfig().put("publicKeySignatureVerifier", signingCertificate);
|
||||
}
|
||||
|
||||
public boolean isValidateSignature() {
|
||||
return Boolean.valueOf(getConfig().get("validateSignature"));
|
||||
}
|
||||
|
||||
public void setValidateSignature(boolean validateSignature) {
|
||||
getConfig().put("validateSignature", String.valueOf(validateSignature));
|
||||
}
|
||||
|
||||
public boolean isBackchannelSupported() {
|
||||
return Boolean.valueOf(getConfig().get("backchannelSupported"));
|
||||
}
|
||||
|
||||
public void setBackchannelSupported(boolean backchannel) {
|
||||
getConfig().put("backchannelSupported", String.valueOf(backchannel));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.oidc.mappers.UsernameTemplateMapper;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKParser;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<OIDCIdentityProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "oidc";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "OpenID Connect v1.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OIDCIdentityProvider create(IdentityProviderModel model) {
|
||||
return new OIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
return parseOIDCConfig(inputStream);
|
||||
}
|
||||
|
||||
protected static Map<String, String> parseOIDCConfig(InputStream inputStream) {
|
||||
OIDCConfigurationRepresentation rep = null;
|
||||
try {
|
||||
rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to load openid connect metadata", e);
|
||||
}
|
||||
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel());
|
||||
config.setIssuer(rep.getIssuer());
|
||||
config.setLogoutUrl(rep.getLogoutEndpoint());
|
||||
config.setAuthorizationUrl(rep.getAuthorizationEndpoint());
|
||||
config.setTokenUrl(rep.getTokenEndpoint());
|
||||
config.setUserInfoUrl(rep.getUserinfoEndpoint());
|
||||
if (rep.getJwksUri() != null) {
|
||||
String uri = rep.getJwksUri();
|
||||
String keySetString = null;
|
||||
try {
|
||||
keySetString = SimpleHttp.doGet(uri).asString();
|
||||
JSONWebKeySet keySet = JsonSerialization.readValue(keySetString, JSONWebKeySet.class);
|
||||
for (JWK jwk : keySet.getKeys()) {
|
||||
JWKParser parse = JWKParser.create(jwk);
|
||||
if (parse.getJwk().getPublicKeyUse().equals(JWK.SIG_USE) && keyTypeSupported(jwk.getKeyType())) {
|
||||
PublicKey key = parse.toPublicKey();
|
||||
config.setPublicKeySignatureVerifier(KeycloakModelUtils.getPemFromKey(key));
|
||||
config.setValidateSignature(true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to query JWKSet from: " + uri, e);
|
||||
}
|
||||
|
||||
}
|
||||
return config.getConfig();
|
||||
}
|
||||
|
||||
protected static boolean keyTypeSupported(String type) {
|
||||
return type != null && type.equals("RSA");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper {
|
||||
public static final String CLAIM = "claim";
|
||||
public static final String CLAIM_VALUE = "claim.value";
|
||||
|
||||
public static Object getClaimValue(JsonWebToken token, String claim) {
|
||||
String[] split = claim.split("\\.");
|
||||
Map<String, Object> jsonObject = token.getOtherClaims();
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
if (i == split.length - 1) {
|
||||
return jsonObject.get(split[i]);
|
||||
} else {
|
||||
Object val = jsonObject.get(split[i]);
|
||||
if (!(val instanceof Map)) return null;
|
||||
jsonObject = (Map<String, Object>)val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String claim = mapperModel.getConfig().get(CLAIM);
|
||||
return getClaimValue(context, claim);
|
||||
}
|
||||
|
||||
public static Object getClaimValue(BrokeredIdentityContext context, String claim) {
|
||||
{ // search access token
|
||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
||||
if (token != null) {
|
||||
Object value = getClaimValue(token, claim);
|
||||
if (value != null) return value;
|
||||
}
|
||||
|
||||
}
|
||||
{ // search ID Token
|
||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN);
|
||||
if (token != null) {
|
||||
Object value = getClaimValue(token, claim);
|
||||
if (value != null) return value;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected boolean hasClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
Object value = getClaimValue(mapperModel, context);
|
||||
String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE);
|
||||
return valueEquals(desiredValue, value);
|
||||
}
|
||||
|
||||
public boolean valueEquals(String desiredValue, Object value) {
|
||||
if (value instanceof String) {
|
||||
if (desiredValue.equals(value)) return true;
|
||||
} else if (value instanceof Double) {
|
||||
try {
|
||||
if (Double.valueOf(desiredValue).equals(value)) return true;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
} else if (value instanceof Integer) {
|
||||
try {
|
||||
if (Integer.valueOf(desiredValue).equals(value)) return true;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
} else if (value instanceof Boolean) {
|
||||
try {
|
||||
if (Boolean.valueOf(desiredValue).equals(value)) return true;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
} else if (value instanceof List) {
|
||||
List list = (List)value;
|
||||
for (Object val : list) {
|
||||
if (valueEquals(desiredValue, val)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
-206
@@ -1,206 +0,0 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProvider;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
/**
|
||||
* Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user
|
||||
* attribute. Concrete mapper classes with own ID and provider mapping must be implemented for each social provider who
|
||||
* uses {@link JsonNode} user profile.
|
||||
*
|
||||
* @author Vlastimil Elias (velias at redhat dot com)
|
||||
*/
|
||||
public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper {
|
||||
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class);
|
||||
|
||||
protected static final Logger LOGGER_DUMP_USER_PROFILE = Logger.getLogger("org.keycloak.social.user_profile_dump");
|
||||
|
||||
private static final String JSON_PATH_DELIMITER = ".";
|
||||
|
||||
/**
|
||||
* Config param where name of mapping source JSON User Profile field is stored.
|
||||
*/
|
||||
public static final String CONF_JSON_FIELD = "jsonField";
|
||||
/**
|
||||
* Config param where name of mapping target USer attribute is stored.
|
||||
*/
|
||||
public static final String CONF_USER_ATTRIBUTE = "userAttribute";
|
||||
|
||||
/**
|
||||
* Key in {@link BrokeredIdentityContext#getContextData()} where {@link JsonNode} with user profile is stored.
|
||||
*/
|
||||
public static final String CONTEXT_JSON_NODE = OIDCIdentityProvider.USER_INFO;
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
ProviderConfigProperty property1;
|
||||
property1 = new ProviderConfigProperty();
|
||||
property1.setName(CONF_JSON_FIELD);
|
||||
property1.setLabel("Social Profile JSON Field Path");
|
||||
property1.setHelpText("Path of field in Social provider User Profile JSON data to get value from. You can use dot notation for nesting and square brackets for array index. Eg. 'contact.address[0].country'.");
|
||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property1);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(CONF_USER_ATTRIBUTE);
|
||||
property.setLabel("User Attribute Name");
|
||||
property.setHelpText("User attribute name to store information into.");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store used profile JsonNode into user context for later use by this mapper. Profile data are dumped into special logger if enabled also to allow investigation of the structure.
|
||||
*
|
||||
* @param user context to store profile data into
|
||||
* @param profile to store into context
|
||||
* @param provider identification of social provider to be used in log dump
|
||||
*
|
||||
* @see #preprocessFederatedIdentity(KeycloakSession, RealmModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
||||
* @see BrokeredIdentityContext#getContextData()
|
||||
*/
|
||||
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
||||
user.getContextData().put(AbstractJsonUserAttributeMapper.CONTEXT_JSON_NODE, profile);
|
||||
if (LOGGER_DUMP_USER_PROFILE.isDebugEnabled())
|
||||
LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data for provider "+provider+": " + profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Attribute Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Attribute Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Import user profile information if it exists in Social provider JSON data into the specified user attribute.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
||||
if (attribute == null || attribute.trim().isEmpty()) {
|
||||
logger.warnf("Attribute is not configured for mapper %s", mapperModel.getName());
|
||||
return;
|
||||
}
|
||||
attribute = attribute.trim();
|
||||
|
||||
String value = getJsonValue(mapperModel, context);
|
||||
if (value != null) {
|
||||
context.setUserAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
// we do not update user profile from social provider
|
||||
}
|
||||
|
||||
protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
||||
if (jsonField == null || jsonField.trim().isEmpty()) {
|
||||
logger.warnf("JSON field path is not configured for mapper %s", mapperModel.getName());
|
||||
return null;
|
||||
}
|
||||
jsonField = jsonField.trim();
|
||||
|
||||
if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
|
||||
logger.warnf("JSON field path is invalid %s", jsonField);
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE);
|
||||
|
||||
String value = getJsonValue(profileJsonNode, jsonField);
|
||||
|
||||
if (value == null) {
|
||||
logger.debugf("User profile JSON value '%s' is not available.", jsonField);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected static String getJsonValue(JsonNode baseNode, String fieldPath) {
|
||||
logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode);
|
||||
if (baseNode != null) {
|
||||
|
||||
int idx = fieldPath.indexOf(JSON_PATH_DELIMITER);
|
||||
|
||||
String currentFieldName = fieldPath;
|
||||
if (idx > 0) {
|
||||
currentFieldName = fieldPath.substring(0, idx).trim();
|
||||
if (currentFieldName.isEmpty()) {
|
||||
logger.debug("JSON path is invalid " + fieldPath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String currentNodeName = currentFieldName;
|
||||
int arrayIndex = -1;
|
||||
if (currentFieldName.endsWith("]")) {
|
||||
int bi = currentFieldName.indexOf("[");
|
||||
if (bi == -1) {
|
||||
logger.debug("Invalid array index construct in " + currentFieldName);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String is = currentFieldName.substring(bi+1, currentFieldName.length() - 1).trim();
|
||||
arrayIndex = Integer.parseInt(is);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Invalid array index construct in " + currentFieldName);
|
||||
return null;
|
||||
}
|
||||
currentNodeName = currentFieldName.substring(0,bi).trim();
|
||||
}
|
||||
|
||||
JsonNode currentNode = baseNode.get(currentNodeName);
|
||||
if (arrayIndex > -1 && currentNode.isArray()) {
|
||||
logger.debug("Going to take array node at index " + arrayIndex);
|
||||
currentNode = currentNode.get(arrayIndex);
|
||||
}
|
||||
|
||||
if (currentNode == null) {
|
||||
logger.debug("JsonNode not found for name " + currentFieldName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (idx < 0) {
|
||||
if (!currentNode.isValueNode()) {
|
||||
logger.debug("JsonNode is not value node for name " + currentFieldName);
|
||||
return null;
|
||||
}
|
||||
String ret = currentNode.asText();
|
||||
if (ret != null && !ret.trim().isEmpty())
|
||||
return ret.trim();
|
||||
} else {
|
||||
return getJsonValue(currentNode, fieldPath.substring(idx + 1));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClaimToRoleMapper extends AbstractClaimMapper {
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
ProviderConfigProperty property1;
|
||||
property1 = new ProviderConfigProperty();
|
||||
property1.setName(CLAIM);
|
||||
property1.setLabel("Claim");
|
||||
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
|
||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property1);
|
||||
property1 = new ProviderConfigProperty();
|
||||
property1.setName(CLAIM_VALUE);
|
||||
property1.setLabel("Claim Value");
|
||||
property1.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array.");
|
||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property1);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(HardcodedRoleMapper.ROLE);
|
||||
property.setLabel("Role");
|
||||
property.setHelpText("Role to grant to user if claim is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "oidc-role-idp-mapper";
|
||||
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Role Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Claim to Role";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
if (hasClaimValue(mapperModel, context)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.grantRole(role);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
if (!hasClaimValue(mapperModel, context)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.deleteRoleMapping(role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "If a claim exists, grant the user the specified realm or application role.";
|
||||
}
|
||||
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID};
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
private static final String EXTERNAL_ROLE = "external.role";
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
ProviderConfigProperty property1;
|
||||
property1 = new ProviderConfigProperty();
|
||||
property1.setName(EXTERNAL_ROLE);
|
||||
property1.setLabel("External role");
|
||||
property1.setHelpText("External role to check for. To reference an application role the syntax is appname.approle, i.e. myapp.myrole.");
|
||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property1);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(HardcodedRoleMapper.ROLE);
|
||||
property.setLabel("Role");
|
||||
property.setHelpText("Role to grant to user if external role is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
|
||||
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Role Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "External Role to Role";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
RoleModel role = hasRole(realm, mapperModel, context);
|
||||
if (role != null) {
|
||||
user.grantRole(role);
|
||||
}
|
||||
}
|
||||
|
||||
private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
||||
//if (token == null) return;
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
||||
String externalRoleName = parseRole[1];
|
||||
String claimName = null;
|
||||
if (parseRole[0] == null) {
|
||||
claimName = "realm_access.roles";
|
||||
} else {
|
||||
claimName = "resource_access." + parseRole[0] + ".roles";
|
||||
}
|
||||
Object claim = getClaimValue(token, claimName);
|
||||
if (valueEquals(externalRoleName, claim)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
return role;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
RoleModel role = hasRole(realm, mapperModel, context);
|
||||
if (role == null) {
|
||||
user.deleteRoleMapping(role);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Looks for an external role in a keycloak access token. If external role exists, grant the user the specified realm or application role.";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserAttributeMapper extends AbstractClaimMapper {
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
public static final String USER_ATTRIBUTE = "user.attribute";
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
ProviderConfigProperty property1;
|
||||
property1 = new ProviderConfigProperty();
|
||||
property1.setName(CLAIM);
|
||||
property1.setLabel("Claim");
|
||||
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
|
||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property1);
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(USER_ATTRIBUTE);
|
||||
property.setLabel("User Attribute Name");
|
||||
property.setHelpText("User attribute name to store claim.");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "oidc-user-attribute-idp-mapper";
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Attribute Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Attribute Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getClaimValue(mapperModel, context);
|
||||
if (value != null) {
|
||||
context.setUserAttribute(attribute, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getClaimValue(mapperModel, context);
|
||||
String current = user.getFirstAttribute(attribute);
|
||||
if (value != null && !value.equals(current)) {
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
} else if (value == null) {
|
||||
user.removeAttribute(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Import declared claim if it exists in ID or access token into the specified user attribute.";
|
||||
}
|
||||
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
package org.keycloak.broker.oidc.mappers;
|
||||
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UsernameTemplateMapper extends AbstractClaimMapper {
|
||||
|
||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
public static final String TEMPLATE = "template";
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(TEMPLATE);
|
||||
property.setLabel("Template");
|
||||
property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${CLAIM.sub}'. ALIAS is the provider alias. CLAIM.<NAME> references an ID or Access token claim.");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
property.setDefaultValue("${ALIAS}.${CLAIM.preferred_username}");
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "oidc-username-idp-mapper";
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getCompatibleProviders() {
|
||||
return COMPATIBLE_PROVIDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayCategory() {
|
||||
return "Preprocessor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Username Template Importer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
}
|
||||
|
||||
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
|
||||
|
||||
@Override
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String template = mapperModel.getConfig().get(TEMPLATE);
|
||||
Matcher m = substitution.matcher(template);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String variable = m.group(1);
|
||||
if (variable.equals("ALIAS")) {
|
||||
m.appendReplacement(sb, context.getIdpConfig().getAlias());
|
||||
} else if (variable.equals("UUID")) {
|
||||
m.appendReplacement(sb, KeycloakModelUtils.generateId());
|
||||
} else if (variable.startsWith("CLAIM.")) {
|
||||
String name = variable.substring("CLAIM.".length());
|
||||
Object value = AbstractClaimMapper.getClaimValue(context, name);
|
||||
if (value == null) value = "";
|
||||
m.appendReplacement(sb, value.toString());
|
||||
} else {
|
||||
m.appendReplacement(sb, m.group(1));
|
||||
}
|
||||
|
||||
}
|
||||
m.appendTail(sb);
|
||||
String username = sb.toString();
|
||||
context.setModelUsername(username);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Format the username to import.";
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.keycloak.broker.oidc.util;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class JsonSimpleHttp extends SimpleHttp {
|
||||
public JsonSimpleHttp(String url, String method) {
|
||||
super(url, method);
|
||||
}
|
||||
|
||||
public static JsonSimpleHttp doGet(String url) {
|
||||
return new JsonSimpleHttp(url, "GET");
|
||||
}
|
||||
|
||||
public static JsonSimpleHttp doPost(String url) {
|
||||
return new JsonSimpleHttp(url, "POST");
|
||||
}
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static JsonNode asJson(SimpleHttp request) throws IOException {
|
||||
return mapper.readTree(request.asString());
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 5091 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user