merge conflicts

This commit is contained in:
Bill Burke 2015-04-15 11:18:47 -04:00
commit edb9f0cecf
139 changed files with 1799 additions and 816 deletions

101
README.md
View file

@ -1,39 +1,72 @@
keycloak
Keycloak
========
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
and video tutorials.
Keycloak is an SSO Service for web apps and REST services. For more information visit [http://keycloak.org](http://keycloak.org).
Keycloak is an SSO Service for web apps and REST services.
It can be used for social applications as well as enterprise applications. It is based on OpenID Connect with support for SAML 2.0 as well.
Here's some of the features:
* SSO and Single Log Out for browser applications
* Social Broker. Enable Google, Facebook, Yahoo, Twitter, GitHub, LinkedIn social login with no code required.
* Optional LDAP/Active Directory integration
* Optional User Registration
* Password and TOTP support (via Google Authenticator or FreeOTP). Client cert auth coming soon.
* User session management from both admin and user perspective
* Customizable themes for user facing pages: login, grant pages, account management, emails, and admin console all customizable!
* OAuth Bearer token auth for REST Services
* Integrated Browser App to REST Service token propagation
* Admin REST API
* OAuth 2.0 Grant requests
* CORS Support
* CORS Web Origin management and validation
* Completely centrally managed user and role mapping metadata. Minimal configuration at the application side
* Admin Console for managing users, roles, role mappings, applications, user sessions, allowed CORS web origins, and OAuth clients.
* Deployable as a WAR, appliance, or an Openshift cloud service (SaaS).
* Supports JBoss AS7, EAP 6.x, Wildfly, Tomcat, and Jetty applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java applications.
* Javascript/HTML 5 adapter for pure Javascript apps
* Session management from admin console
* Revocation policies
* Password policies
* OpenID Connect Support
* SAML Support
* Token claim and SAML assertion mappings, role name mappings, etc. Ability to configure exactly what information you want in your tokens and SAML documents
* IDP brokering or chaining. You can set up Keycloak to be a child IDP to another SAML or OIDC IDP.
* Kerberos bridging. Logged in Kerberos users can access Keycloak SAML or OIDC applications via our Kerberos bridge.
Building
--------
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
and video tutorials.
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)

View file

@ -33,8 +33,8 @@ 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 org.keycloak.services.resources.flows.Flows;
import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;
@ -215,7 +215,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
event.event(EventType.LOGIN);
event.error(error);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
try {
@ -240,7 +240,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
}
event.event(EventType.LOGIN);
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
public SimpleHttp generateTokenRequest(String authorizationCode) {

View file

@ -30,16 +30,15 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
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.services.resources.flows.Flows;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.PemUtils;
@ -120,14 +119,14 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
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 Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
}

View file

@ -39,12 +39,15 @@ import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
@ -133,18 +136,18 @@ public class SAMLEndpoint {
if (!checkSsl()) {
event.event(EventType.LOGIN);
event.error(Errors.SSL_REQUIRED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
}
if (!realm.isEnabled()) {
event.event(EventType.LOGIN_ERROR);
event.error(Errors.REALM_DISABLED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
}
if (samlRequest == null && samlResponse == null) {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_REQUEST);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
return null;
@ -180,7 +183,7 @@ public class SAMLEndpoint {
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SAML_RESPONSE);
event.detail(Details.REASON, "invalid_destination");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
if (config.isValidateSignature()) {
try {
@ -189,7 +192,7 @@ public class SAMLEndpoint {
logger.error("validation failed", e);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SIGNATURE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
}
}
@ -202,7 +205,7 @@ public class SAMLEndpoint {
} else {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
}
@ -335,7 +338,7 @@ public class SAMLEndpoint {
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SAML_RESPONSE);
event.detail(Details.REASON, "invalid_destination");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
}
if (config.isValidateSignature()) {
try {
@ -344,7 +347,7 @@ public class SAMLEndpoint {
logger.error("validation failed", e);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SIGNATURE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
}
}
if (statusResponse instanceof ResponseType) {
@ -363,20 +366,20 @@ public class SAMLEndpoint {
logger.error("no valid user session");
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
if (userSession == null) {
logger.error("no valid user session");
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
logger.error("usersession in different state");
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
}

View file

@ -30,7 +30,6 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;

View file

@ -32,6 +32,9 @@
<constraints nullable="false"/>
</column>
</createTable>
<addColumn tableName="CREDENTIAL">
<column name="CREATED_DATE" type="BIGINT"/>
</addColumn>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_IDPM" tableName="IDENTITY_PROVIDER_MAPPER"/>
<addPrimaryKey columnNames="IDP_MAPPER_ID, NAME" constraintName="CONSTRAINT_IDPMConfig" tableName="IDP_MAPPER_CONFIG"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER_MAPPER" constraintName="FK_IDPM_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
@ -48,5 +51,23 @@
</update>
<dropColumn tableName="CLIENT" columnName="DTYPE"/>
<renameColumn tableName="CLIENT" newColumnName="CLIENT_ID" oldColumnName="NAME"/>
<renameColumn tableName="REALM" newColumnName="MASTER_ADMIN_CLIENT" oldColumnName="MASTER_ADMIN_APP"/>
<renameTable oldTableName="REALM_APPLICATION" newTableName="REALM_CLIENT"/>
<renameColumn tableName="REALM_CLIENT" newColumnName="CLIENT_ID" oldColumnName="APPLICATION_ID"/>
<renameTable oldTableName="APPLICATION_DEFAULT_ROLES" newTableName="CLIENT_DEFAULT_ROLES"/>
<renameColumn tableName="CLIENT_DEFAULT_ROLES" newColumnName="CLIENT_ID" oldColumnName="APPLICATION_ID"/>
<renameTable oldTableName="APP_NODE_REGISTRATIONS" newTableName="CLIENT_NODE_REGISTRATIONS"/>
<renameColumn tableName="CLIENT_NODE_REGISTRATIONS" newColumnName="CLIENT_ID" oldColumnName="APPLICATION_ID"/>
<renameColumn tableName="KEYCLOAK_ROLE" newColumnName="CLIENT" oldColumnName="APPLICATION"/>
<renameColumn tableName="KEYCLOAK_ROLE" newColumnName="CLIENT_ROLE" oldColumnName="APPLICATION_ROLE"/>
<renameColumn tableName="KEYCLOAK_ROLE" newColumnName="CLIENT_REALM_CONSTRAINT" oldColumnName="APP_REALM_CONSTRAINT"/>
<dropUniqueConstraint tableName="KEYCLOAK_ROLE" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2"/>
<addUniqueConstraint columnNames="NAME,CLIENT_REALM_CONSTRAINT" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2-2" tableName="KEYCLOAK_ROLE"/>
</changeSet>
</databaseChangeLog>

View file

@ -19,6 +19,9 @@ public class Update1_2_0_RC1 extends Update {
public void update(KeycloakSession session) {
convertApplicationsToClients();
convertOAuthClientsToClients();
db.getCollection("realms").update(new BasicDBObject(), new BasicDBObject("$rename", new BasicDBObject("adminAppId", "clientId")), false, true);
}
private void convertApplicationsToClients() {
@ -31,6 +34,10 @@ public class Update1_2_0_RC1 extends Update {
DBCollection roles = db.getCollection("roles");
roles.update(new BasicDBObject(), new BasicDBObject("$rename", new BasicDBObject("applicationId", "clientId")), false, true);
log.debugv("Renamed roles.applicationId to roles.clientId");
db.getCollection("clients").dropIndex("realmId_1_name_1");
ensureIndex("clients", new String[]{"realmId", "clientId"}, true, false);
}
private void convertOAuthClientsToClients() {

View file

@ -12,6 +12,7 @@
<module name="org.keycloak.keycloak-events-api"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
<module name="javax.ws.rs.api"/>
</dependencies>
</module>

View file

@ -128,7 +128,8 @@
<para>
In the admin console, per realm, you can set up a password policy to enforce that users pick hard to guess passwords.
A password has to match all policies. The password policies that can be configured are hash iterations, length, digits,
lowercase, uppercase, special characters, not username and regex patterns. Multiple regex patterns, separated by comma,
lowercase, uppercase, special characters, not username, regex patterns and expired passwords. Expired Passwords policy
restricts a user from resetting his password to N old expired passwords. Multiple regex patterns, separated by comma,
can be specified. If there's more than one regex added, password has to match all fully.
Increasing number of Hash Iterations (n) does not worsen anything (and certainly not the cipher) and it greatly increases the
resistance to dictionary attacks. However the drawback to increasing n is that it has some cost (CPU usage, energy, delay) for

View file

@ -36,6 +36,11 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View file

@ -3,23 +3,35 @@ package org.keycloak.events.log;
import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import java.util.Map;
import java.util.logging.Level;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JBossLoggingEventListenerProvider implements EventListenerProvider {
private final KeycloakSession session;
private final Logger logger;
private final Logger.Level successLevel;
private final Logger.Level errorLevel;
public JBossLoggingEventListenerProvider(Logger logger) {
public JBossLoggingEventListenerProvider(KeycloakSession session, Logger logger, Logger.Level successLevel, Logger.Level errorLevel) {
this.session = session;
this.logger = logger;
this.successLevel = successLevel;
this.errorLevel = errorLevel;
}
@Override
public void onEvent(Event event) {
Logger.Level level = event.getError() != null ? Logger.Level.WARN : Logger.Level.INFO;
Logger.Level level = event.getError() != null ? errorLevel : successLevel;
if (logger.isEnabled(level)) {
StringBuilder sb = new StringBuilder();
@ -55,7 +67,31 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
}
}
logger.log(level, sb.toString());
if (logger.isTraceEnabled()) {
KeycloakContext context = session.getContext();
UriInfo uriInfo = context.getUri();
HttpHeaders headers = context.getRequestHeaders();
if (uriInfo != null) {
sb.append(", requestUri=");
sb.append(uriInfo.getRequestUri().toString());
}
if (headers != null) {
sb.append(", cookies=[");
boolean f = true;
for (Map.Entry<String, Cookie> e : headers.getCookies().entrySet()) {
if (f) {
f = false;
} else {
sb.append(", ");
}
sb.append(e.getValue().toString());
}
sb.append("]");
}
}
logger.log(logger.isTraceEnabled() ? Logger.Level.TRACE : level, sb.toString());
}
}

View file

@ -6,6 +6,9 @@ import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import sun.rmi.runtime.Log;
import java.util.logging.Level;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -16,13 +19,18 @@ public class JBossLoggingEventListenerProviderFactory implements EventListenerPr
private static final Logger logger = Logger.getLogger("org.keycloak.events");
private Logger.Level successLevel;
private Logger.Level errorLevel;
@Override
public EventListenerProvider create(KeycloakSession session) {
return new JBossLoggingEventListenerProvider(logger);
return new JBossLoggingEventListenerProvider(session, logger, successLevel, errorLevel);
}
@Override
public void init(Config.Scope config) {
successLevel = Logger.Level.valueOf(config.get("success-level", "debug").toUpperCase());
errorLevel = Logger.Level.valueOf(config.get("error-level", "warn").toUpperCase());
}
@Override

View file

@ -24,7 +24,7 @@
],
"clients": [
{
"name": "examples-admin-client",
"clientId": "examples-admin-client",
"enabled": true,
"fullScopeAllowed": true,
"baseUrl": "/examples-admin-client",

View file

@ -43,7 +43,7 @@
},
"clients": [
{
"name": "basic-auth-service",
"clientId": "basic-auth-service",
"enabled": true,
"adminUrl": "/basicauth",
"baseUrl": "/basicauth",

View file

@ -32,7 +32,7 @@
},
"clients": [
{
"name": "facebook-authentication",
"clientId": "facebook-authentication",
"enabled": true,
"publicClient" : true,
"adminUrl": "/facebook-authentication",

View file

@ -32,7 +32,7 @@
},
"clients": [
{
"name": "google-authentication",
"clientId": "google-authentication",
"enabled": true,
"publicClient" : true,
"adminUrl": "/google-authentication",

View file

@ -32,7 +32,7 @@
},
"clients": [
{
"name": "saml-broker-authentication",
"clientId": "saml-broker-authentication",
"enabled": true,
"publicClient" : true,
"adminUrl": "/saml-broker-authentication",

View file

@ -28,7 +28,7 @@
},
"clients": [
{
"name": "http://localhost:8080/auth/realms/saml-broker-authentication-realm",
"clientId": "http://localhost:8080/auth/realms/saml-broker-authentication-realm",
"protocol": "saml",
"enabled": true,
"redirectUris": [

View file

@ -33,7 +33,7 @@
},
"clients": [
{
"name": "twitter-authentication",
"clientId": "twitter-authentication",
"enabled": true,
"publicClient" : true,
"adminUrl": "/twitter-authentication",
@ -52,7 +52,7 @@
]
},
{
"name": "admin-client",
"clientId": "admin-client",
"enabled": true,
"fullScopeAllowed": true,
"baseUrl": "/admin-client",

View file

@ -43,7 +43,7 @@
],
"clients": [
{
"name": "cordova",
"clientId": "cordova",
"enabled": true,
"publicClient": true,
"redirectUris": ["http://localhost"],

View file

@ -44,7 +44,7 @@
],
"clients": [
{
"name": "angular-cors-product",
"clientId": "angular-cors-product",
"enabled": true,
"publicClient": true,
"baseUrl": "http://localhost:8080/angular-cors-product/index.html",

View file

@ -94,7 +94,7 @@
],
"clients": [
{
"name": "customer-portal",
"clientId": "customer-portal",
"enabled": true,
"adminUrl": "/customer-portal",
"baseUrl": "/customer-portal",
@ -104,7 +104,7 @@
"secret": "password"
},
{
"name": "customer-portal-js",
"clientId": "customer-portal-js",
"enabled": true,
"publicClient": true,
"baseUrl": "/customer-portal-js",
@ -113,7 +113,7 @@
]
},
{
"name": "angular-product",
"clientId": "angular-product",
"enabled": true,
"publicClient": true,
"baseUrl": "/angular-product/index.html",
@ -122,7 +122,7 @@
]
},
{
"name": "customer-portal-cli",
"clientId": "customer-portal-cli",
"enabled": true,
"publicClient": true,
"redirectUris": [
@ -131,7 +131,7 @@
]
},
{
"name": "product-portal",
"clientId": "product-portal",
"enabled": true,
"adminUrl": "/product-portal",
"baseUrl": "/product-portal",
@ -141,14 +141,14 @@
"secret": "password"
},
{
"name": "database-service",
"clientId": "database-service",
"enabled": true,
"adminUrl": "/database",
"baseUrl": "/database",
"bearerOnly": true
},
{
"name": "third-party",
"clientId": "third-party",
"enabled": true,
"consentRequired": true,
"redirectUris": [
@ -158,7 +158,7 @@
"secret": "password"
},
{
"name": "admin-client",
"clientId": "admin-client",
"enabled": true,
"publicClient": true,
"directGrantsOnly": true,

View file

@ -136,7 +136,7 @@
},
"clients": [
{
"name": "customer-portal",
"clientId": "customer-portal",
"enabled": true,
"adminUrl": "http://localhost:8181/customer-portal",
"baseUrl": "http://localhost:8181/customer-portal",
@ -146,7 +146,7 @@
"secret": "password"
},
{
"name": "product-portal",
"clientId": "product-portal",
"enabled": true,
"adminUrl": "http://localhost:8181/product-portal",
"baseUrl": "http://localhost:8181/product-portal",
@ -156,7 +156,7 @@
"secret": "password"
},
{
"name": "builtin-cxf-app",
"clientId": "builtin-cxf-app",
"enabled": true,
"adminUrl": "http://localhost:8181/cxf",
"baseUrl": "http://localhost:8181/cxf",
@ -166,21 +166,21 @@
"secret": "password"
},
{
"name": "custom-cxf-endpoint",
"clientId": "custom-cxf-endpoint",
"enabled": true,
"adminUrl": "http://localhost:8282/PersonServiceCF",
"baseUrl": "http://localhost:8282/PersonServiceCF",
"bearerOnly": true
},
{
"name": "admin-camel-endpoint",
"clientId": "admin-camel-endpoint",
"enabled": true,
"adminUrl": "http://localhost:8383/admin-camel-endpoint",
"baseUrl": "http://localhost:8383/admin-camel-endpoint",
"bearerOnly": true
},
{
"name": "ssh-jmx-admin-client",
"clientId": "ssh-jmx-admin-client",
"enabled": true,
"publicClient": false,
"directGrantsOnly": true,
@ -194,4 +194,4 @@
"roles": [ "admin", "jmxAdmin" ]
}
]
}
}

View file

@ -43,7 +43,7 @@
],
"clients": [
{
"name": "js-console",
"clientId": "js-console",
"enabled": true,
"publicClient": true,
"baseUrl": "/js-console",

View file

@ -15,7 +15,7 @@
],
"clients": [
{
"name": "kerberos-app",
"clientId": "kerberos-app",
"enabled": true,
"baseUrl": "/kerberos-portal",
"redirectUris": [
@ -91,4 +91,4 @@
}
}
]
}
}

View file

@ -42,7 +42,7 @@
],
"clients": [
{
"name": "multi-tenant",
"clientId": "multi-tenant",
"enabled": true,
"adminUrl": "/multitenant/tenant1",
"baseUrl": "/multitenant/tenant1",

View file

@ -47,7 +47,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>

View file

@ -6,7 +6,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;

View file

@ -2,7 +2,7 @@ package org.keycloak.account.freemarker.model;
import org.keycloak.freemarker.Theme;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import java.net.URI;

View file

@ -89,12 +89,14 @@ identityProviderRemovedMessage=Identity Provider erfolgreich entfernt.
accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
accountTemporarilyDisabledMessage=Benutzerkonto ist tempor\u00E4r gesperrt, bitte kontaktieren Sie den Admin oder versuchen Sie es sp\u00E4ter noch einmal.
invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort: minimum l\u00E4nge {0}.
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort\: minimum l\u00E4nge {0}.
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Zahl(en) beinhalten.
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Kleinbuchstaben beinhalten.
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Grossbuchstaben beinhalten.
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort\: nicht Regex-Muster (n) entsprechen.
invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort: darf nicht gleich einem der letzten {0} Passwortgeschichte.
locale_de=Deutsch
locale_en=Englisch

View file

@ -93,7 +93,9 @@ invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
locale_de=German
locale_en=English

View file

@ -1,3 +1,18 @@
doSave=Salvar
doCancel=Cancelar
doLogOutAllSessions=Sair de todas as sess\u00F5es
doRemove=Remover
doAdd=Adicionar
doSignOut=Sair
editAccountHtmlTtile=Editar Conta
federatedIdentitiesHtmlTitle=Identidades Federadas
accountLogHtmlTitle=Log da conta
changePasswordHtmlTitle=Alterar senha
sessionsHtmlTitle=Sess\u00F5es
accountManagementTitle=Gerenciamento de Contas Keycloak
authenticatorTitle=Autenticator
authenticatorCode=C\u00F3digo autenticador
email=Email
firstName=Primeiro nome
@ -12,6 +27,36 @@ region=Estado
postal_code=CEP
country=Pa\u00EDs
requiredFields=Campos obrigat\u00F3rios
allFieldsRequired=Todos os campos s\u00E3o obrigat\u00F3rios
backToApplication=&laquo; Voltar para aplica\u00E7\u00E3o
backTo=Voltar para {0}
date=Data
event=Evento
ip=IP
client=Cliente
clients=Clientes
details=Detalhes
started=Iniciado
lastAccess=\u00DAltimo acesso
expires=Expira
applications=Aplica\u00E7\u00F5es
account=Account
federatedIdentity=Federated Identity
authenticator=Authenticator
sessions=Sessions
log=Log
configureAuthenticators=Autenticadores Configurados
mobile=Mobile
totpStep1=Instalar <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> em seu dispositivo mobile.
totpStep2=Abra o aplicativo e escaneie o c\u00F3digo de barras ou digite o c\u00F3digo.
totpStep3=Digite o c\u00F3digo fornecido pelo aplicativo e clique em Salvar para concluir a configura\u00E7\u00E3o.
missingFirstNameMessage=Por favor, informe o primeiro nome.
invalidEmailMessage=E-mail inv\u00E1lido.
missingLastNameMessage=Por favor, informe o sobrenome.
@ -41,18 +86,18 @@ federatedIdentityRemovingLastProviderMessage=Voc\u00EA n\u00E3o pode remover a \
identityProviderRedirectErrorMessage=Falha ao redirecionar para o provedor de identidade
identityProviderRemovedMessage=Provedor de identidade removido com sucesso
accountDisabledMessage=Conta desativada, contate administrador
doLogOutAllSessions=Sair de todas sess\u00F5es
accountDisabledMessage=Conta desativada, contate o administrador
accountTemporarilyDisabledMessage=A conta est\u00E1 temporariamente indispon\u00EDvel, contate administrador ou tente novamente mais tarde
invalidPasswordMinLengthMessage=Senha inv\u00E1lida: comprimento m\u00EDnimo {0}
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres min\u00FAsculos
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres mai\u00FAsculos
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres especiais
invalidPasswordMinLengthMessage=Senha inv\u00E1lida\: comprimento m\u00EDnimo {0}
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres min\u00FAsculos
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres mai\u00FAsculos
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres especiais
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
invalidPasswordRegexPatternMessage=Senha inv\u00E1lida\: n\u00E3o correspondem ao padr\u00E3o regex(s).
invalidPasswordHistoryMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual a qualquer uma {0} \u00FAltima hist\u00F3ria senha.
locale_de=Deutsch
locale_en=English
locale_pt-BR=Portugu\u00EAs (BR)
locale_pt-BR=Portugu\u00EAs (BR)

View file

@ -900,14 +900,15 @@ module.factory('PasswordPolicy', function() {
var p = {};
p.policyMessages = {
hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.",
length: "Minimal password length (integer type). Default value is 8.",
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
notUsername: "Block passwords that are equal to the username",
regexPatterns: "Block passwords that do not match all of the regex patterns (string type)."
hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.",
length: "Minimal password length (integer type). Default value is 8.",
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
notUsername: "Block passwords that are equal to the username",
regexPatterns: "Block passwords that do not match all of the regex patterns (string type).",
passwordHistory: "Block passwords that are equal to previous passwords. Default value is 3."
}
p.allPolicies = [
@ -918,7 +919,8 @@ module.factory('PasswordPolicy', function() {
{ name: 'upperCase', value: 1 },
{ name: 'specialChars', value: 1 },
{ name: 'notUsername', value: 1 },
{ name: 'regexPatterns', value: ''}
{ name: 'regexPatterns', value: ''},
{ name: 'passwordHistory', value: 3 }
];
p.parse = function(policyString) {

View file

@ -125,12 +125,14 @@ accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert.
noAccessMessage=Kein Zugriff
invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort: minimum l\u00E4nge {0}.
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Spezialzeichen beinhalten.
invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort\: minimum l\u00E4nge {0}.
invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Zahl(en) beinhalten.
invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Kleinbuchstaben beinhalten.
invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Grossbuchstaben beinhalten.
invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort\: nicht Regex-Muster (n) entsprechen.
invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort\: darf nicht gleich einem der letzten {0} Passwortgeschichte.
failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
httpsRequiredMessage=HTTPS erforderlich.

View file

@ -69,7 +69,7 @@ personalInfo=Personal Info:
role_admin=Admin
role_realm-admin=Realm Admin
role_create-realm=Crate realm
role_create-realm=Create realm
role_view-realm=View realm
role_view-users=View users
role_view-applications=View applications
@ -127,7 +127,9 @@ invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} nume
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
failedToProcessResponseMessage=Failed to process response
httpsRequiredMessage=HTTPS required

View file

@ -11,8 +11,6 @@ registerWithTitle=Registre-se com {0}
registerWithTitleHtml=Registre-se com <strong>{0}</strong>
loginTitle=Entrar em {0}
loginTitleHtml=Entrar em <strong>{0}</strong>
loginOauthTitle=Acesso tempor\u00E1rio para {0}
loginOauthTitleHtml=Acesso tempor\u00E1rio para <strong>{0}<strong> solicitado por <strong>{1}</strong>.
loginTotpTitle=Configura\u00E7\u00E3o do autenticador mobile
loginProfileTitle=Atualiza\u00E7\u00E3o de Informa\u00E7\u00F5es da Conta
oauthGrantTitle=Concess\u00E3o OAuth
@ -22,12 +20,14 @@ errorTitleHtml=N\u00F3s <strong>lamentamos</strong> ...
emailVerifyTitle=Verifica\u00E7\u00E3o de e-mail
emailForgotTitle=Esqueceu sua senha?
updatePasswordTitle=Atualiza\u00E7\u00E3o de senha
codeSuccessTitle=C\u00F3digo de sucesso
codeErrorTitle=C\u00F3digo de erro\: {0}
noAccount=Novo usu\u00E1rio?
username=Nome de usu\u00E1rio
givenName=Primeiro nome
usernameOrEmail=Nome de usu\u00E1rio ou email
firstName=Primeiro nome
givenName=Primeiro nome
fullName=Nome completo
lastName=Sobrenome
familyName=Sobrenome
@ -36,7 +36,7 @@ password=Senha
passwordConfirm=Confirme a senha
passwordNew=Nova senha
passwordNewConfirm=Confirma\u00E7\u00E3o da nova senha
rememberMe=Relembre-me
rememberMe=Mantenha-me conectado
authenticatorCode=C\u00F3digo autenticador
address=Endere\u00E7o
street=Logradouro
@ -48,11 +48,12 @@ emailVerified=Email verificado
gssDelegationCredential=gss delega\u00E7\u00E3o credencial
loginTotpStep1=Instale <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> em seu celular
loginTotpStep2=Abra o aplicativo e escanei o c\u00F3digo de barras ou digite o c\u00F3digo
loginTotpStep3=Digite o c\u00F3digo autenticador fornecido pelo aplicativo e clique em Enviar para concluir a configura\u00E7\u00E3o
loginTotpStep2=Abra o aplicativo e escaneie o c\u00F3digo de barras ou digite o c\u00F3digo
loginTotpStep3=Digite o c\u00F3digo fornecido pelo aplicativo e clique em Enviar para concluir a configura\u00E7\u00E3o
loginTotpOneTime=C\u00F3digo autenticador
oauthGrantRequest=Voc\u00EA concede esses privil\u00E9gios de acesso?
inResource=em <strong>{0}</strong>
emailVerifyInstruction1=Um e-mail com instru\u00E7\u00F5es para verificar o seu endere\u00E7o de e-mail foi enviado para voc\u00EA.
emailVerifyInstruction2=Voc\u00EA n\u00E3o recebeu um c\u00F3digo de verifica\u00E7\u00E3o em seu e-mail?
@ -62,6 +63,8 @@ backToLogin=&laquo; Voltar
emailInstruction=Digite seu nome de usu\u00E1rio ou endere\u00E7o de email e n\u00F3s lhe enviaremos instru\u00E7\u00F5es sobre como criar uma nova senha.
copyCodeInstruction=Por favor, copie o c\u00F3digo e cole-o em sua aplica\u00E7\u00E3o:
personalInfo=Informa\u00E7\u00F5es pessoais:
role_admin=Admin
@ -119,12 +122,14 @@ accountPasswordUpdatedMessage=Sua senha foi atualizada
noAccessMessage=Sem acesso
invalidPasswordMinLengthMessage=Senha inv\u00E1lida: comprimento m\u00EDnimo {0}
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres min\u00FAsculos
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres mai\u00FAsculos
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida: deve conter pelo menos {0} caracteres especiais
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida: n\u00E3o deve ser igual ao nome de usu\u00E1rio
invalidPasswordMinLengthMessage=Senha inv\u00E1lida\: comprimento m\u00EDnimo {0}
invalidPasswordMinDigitsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres min\u00FAsculos
invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres mai\u00FAsculos
invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres especiais
invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
invalidPasswordRegexPatternMessage=Senha inv\u00E1lida\: n\u00E3o correspondem ao padr\u00E3o regex(s).
invalidPasswordHistoryMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual a qualquer uma {0} \u00FAltima hist\u00F3ria senha.
failedToProcessResponseMessage=Falha ao processar a resposta
httpsRequiredMessage=HTTPS requerido

View file

@ -21,12 +21,6 @@ import org.keycloak.provider.Provider;
*/
public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setRealm(RealmModel realm);
public LoginFormsProvider setUriInfo(UriInfo uriInfo);
public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders);
public Response createResponse(UserModel.RequiredAction action);
public Response createLogin();
@ -67,14 +61,8 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setSuccess(String message, Object ... parameters);
public LoginFormsProvider setWarning(String message, Object ... parameters);
public LoginFormsProvider setUser(UserModel user);
public LoginFormsProvider setClient(ClientModel client);
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams);
public LoginFormsProvider setResponseHeader(String headerName, String headerValue);
public LoginFormsProvider setFormData(MultivaluedMap<String, String> formData);

View file

@ -37,9 +37,8 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@ -79,39 +78,20 @@ import java.util.concurrent.TimeUnit;
private KeycloakSession session;
private FreeMarkerUtil freeMarker;
private RealmModel realm;
private UserModel user;
private ClientModel client;
private ClientSessionModel clientSession;
private UriInfo uriInfo;
private HttpHeaders httpHeaders;
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session;
this.freeMarker = freeMarker;
}
public LoginFormsProvider setRealm(RealmModel realm) {
this.realm = realm;
return this;
}
public LoginFormsProvider setUriInfo(UriInfo uriInfo) {
this.uriInfo = uriInfo;
return this;
}
@Override
public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
return this;
}
public Response createResponse(UserModel.RequiredAction action) {
RealmModel realm = session.getContext().getRealm();
UriInfo uriInfo = session.getContext().getUri();
String actionMessage;
LoginFormsPages page;
@ -150,13 +130,17 @@ import java.util.concurrent.TimeUnit;
}
if (messages == null) {
setWarning(actionMessage);
setMessage(MessageType.WARNING, actionMessage);
}
return createResponse(page);
}
private Response createResponse(LoginFormsPages page) {
RealmModel realm = session.getContext().getRealm();
ClientModel client = session.getContext().getClient();
UriInfo uriInfo = session.getContext().getUri();
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
String requestURI = uriInfo.getBaseUri().getPath();
@ -191,7 +175,7 @@ import java.util.concurrent.TimeUnit;
}
Properties messagesBundle;
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders);
Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, session.getContext().getRequestHeaders());
try {
messagesBundle = theme.getMessages(locale);
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
@ -222,7 +206,7 @@ import java.util.concurrent.TimeUnit;
if (realm != null) {
attributes.put("realm", new RealmBean(realm));
attributes.put("social", new IdentityProviderBean(realm, baseUri, this.uriInfo));
attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
if (realm.isInternationalizationEnabled()) {
@ -365,22 +349,11 @@ import java.util.concurrent.TimeUnit;
return this;
}
@Override
public FreeMarkerLoginFormsProvider setWarning(String message, Object ... parameters) {
setMessage(MessageType.WARNING, message, parameters);
return this;
}
public FreeMarkerLoginFormsProvider setUser(UserModel user) {
this.user = user;
return this;
}
public FreeMarkerLoginFormsProvider setClient(ClientModel client) {
this.client = client;
return this;
}
public FreeMarkerLoginFormsProvider setFormData(MultivaluedMap<String, String> formData) {
this.formData = formData;
return this;
@ -411,12 +384,6 @@ import java.util.concurrent.TimeUnit;
return this;
}
@Override
public LoginFormsProvider setQueryParams(MultivaluedMap<String, String> queryParams) {
this.queryParams = queryParams;
return this;
}
@Override
public LoginFormsProvider setActionUri(URI actionUri) {
this.actionUri = actionUri;

View file

@ -23,7 +23,7 @@ package org.keycloak.login.freemarker.model;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.core.UriInfo;
import java.net.URI;

View file

@ -23,7 +23,7 @@ package org.keycloak.login.freemarker.model;
import org.keycloak.freemarker.Theme;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import java.net.URI;

View file

@ -26,7 +26,7 @@ public class Keycloak {
target = client.target(config.getServerUrl());
target.register(new BearerAuthFilter(tokenManager.getAccessTokenString()));
target.register(new BearerAuthFilter(tokenManager));
}
public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){

View file

@ -1,5 +1,7 @@
package org.keycloak.admin.client.resource;
import org.keycloak.admin.client.token.TokenManager;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.core.HttpHeaders;
@ -11,14 +13,23 @@ import java.io.IOException;
public class BearerAuthFilter implements ClientRequestFilter {
private final String tokenString;
private final TokenManager tokenManager;
public BearerAuthFilter(String tokenString) {
this.tokenString = tokenString;
this.tokenManager = null;
}
public BearerAuthFilter(TokenManager tokenManager) {
this.tokenManager = tokenManager;
this.tokenString = null;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
String authHeader = "Bearer " + tokenString;
String authHeader = "Bearer " + (tokenManager != null ? tokenManager.getAccessTokenString() : tokenString);
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
}

View file

@ -22,7 +22,7 @@ public interface RoleMappingResource {
@Path("realm")
public RoleScopeResource realmLevel();
@Path("applications/{appName}")
public RoleScopeResource applicationLevel(@PathParam("appName") String appName);
@Path("clients/{clientId}")
public RoleScopeResource clientLevel(@PathParam("clientId") String clientId);
}

View file

@ -66,8 +66,7 @@ public class TokenManager {
Form form = new Form()
.param("grant_type", "refresh_token")
.param("username", config.getUsername())
.param("password", config.getPassword());
.param("refresh_token", currentToken.getRefreshToken());
if(config.isPublicClient()){
form.param("client_id", config.getClientId());

View file

@ -1,11 +1,20 @@
Test with various databases
===========================
MongoDB
-------
The Keycloak testsuite uses an embedded MongoDB when running tests so you don't have to have one running locally.
Run tests:
mvn install -Pmongo
MySQL
-----
Use the official [MySQL docker image](https://registry.hub.docker.com/_/mysql/).
The simplest way to test with MySQL is to use the official [MySQL docker image](https://registry.hub.docker.com/_/mysql/).
Start MySQL:
@ -13,7 +22,7 @@ Start MySQL:
Run tests:
mvn clean install -Dkeycloak.connectionsJpa.url=jdbc:mysql://`docker inspect --format '{{ .NetworkSettings.IPAddress }}' mysql`/keycloak -Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak
mvn install -Dkeycloak.connectionsJpa.url=jdbc:mysql://`docker inspect --format '{{ .NetworkSettings.IPAddress }}' mysql`/keycloak -Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak
Stop MySQl:
@ -23,7 +32,7 @@ Stop MySQl:
PostgreSQL
----------
Use the official [PostgreSQL docker image](https://registry.hub.docker.com/_/postgres/).
The simplest way to test with PostgreSQL is to use the official [PostgreSQL docker image](https://registry.hub.docker.com/_/postgres/).
Start PostgreSQL:
@ -31,7 +40,7 @@ Start PostgreSQL:
Run tests:
mvn clean install -Dkeycloak.connectionsJpa.url=jdbc:postgresql://`docker inspect --format '{{ .NetworkSettings.IPAddress }}' postgres`:5432/keycloak -Dkeycloak.connectionsJpa.driver=org.postgresql.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak
mvn install -Dkeycloak.connectionsJpa.url=jdbc:postgresql://`docker inspect --format '{{ .NetworkSettings.IPAddress }}' postgres`:5432/keycloak -Dkeycloak.connectionsJpa.driver=org.postgresql.Driver -Dkeycloak.connectionsJpa.user=keycloak -Dkeycloak.connectionsJpa.password=keycloak
Stop PostgreSQL:

70
misc/HackingOnKeycloak.md Normal file
View file

@ -0,0 +1,70 @@
Hacking on Keycloak
===================
GitHub Repository
-----------------
### Create a GitHub account if you don't already have one
[Join GitHub](https://github.com/join)
### Fork Keycloak repository into your account
[https://github.com/keycloak/keycloak](https://github.com/keycloak/keycloak)
### Clone your newly forked copy onto your local workspace
git clone https://github.com/<your username>/keycloak.git
cd keycloak
### Add a remote ref to upstream for pulling future updates
git remote add upstream https://github.com/keycloak/keycloak.git
### Pull later updates from upstream
git fetch upstream
git rebase upstream/master
Discuss changes
---------------
Before starting work on a new feature or anything besides a minor bug fix join the [Keycloak Dev mailing list](https://lists.jboss.org/mailman/listinfo/keycloak-dev)
and send a mail about your proposed changes. This is vital as otherwise you may waste days implementing a feature that is later rejected.
Once you have received feedback from the mailing list if there's not one already create a (JIRA issue)[https://issues.jboss.org/browse/KEYCLOAK].
Implement changes
-----------------
We don't currently enforce a code style in Keycloak, but a good reference is the code style used by WildFly. This can be
retrieved from (https://github.com/wildfly/wildfly-core/tree/master/ide-configs)[https://github.com/wildfly/wildfly-core/tree/master/ide-configs].
If your changes requires updates to the database read [Updating Database Schema](UpdatingDatabaseSchema.md).
To try your changes out manually you can quickly start Keycloak from within your IDEA or Maven, to find out how to do this
read [Testsuite](Testsuite.md). It's also important that you add tests to the testsuite for your changes.
Get your changes merged into upstream
-------------------------------------
Here's a quick check list for a good pull request (PR):
* Discussed and agreed on Keycloak Dev mailing list
* One commit per PR
* One feature/change per PR
* No changes to code not directly related to your change (e.g. no formatting changes or refactoring to existing code, if you want to refactor/improve existing code that's a separate discussion to mailing list and JIRA issue)
* A JIRA associated with your PR (include the JIRA issue number in commit comment)
* All tests in testsuite pass
* Do a rebase on upstream master
Once you're happy with your changes go to GitHub and create a PR.
Release Keycloak
----------------
* [Release Process](ReleaseProcess.md)

80
misc/ReleaseProcess.md Normal file
View file

@ -0,0 +1,80 @@
## Test
* Make sure tests pass on Travis
* Make sure tests pass on Jenkins
* Go through the (manual testing)[https://docs.google.com/spreadsheets/d/17C_WEHNE03r5DxN71OXGJaytjA6_WjZKCXRcsnmNQD4]
## Create release
* Get from github
```
$ git@github.com:keycloak/keycloak.git
```
* Build everything to make sure its kosher.
```
$ cd keycloak
$ mvn install
```
* Build javadoc and jaxrs-doc
```
$ mvn javadoc:javadoc
# This is for jaxrs-docs
$ cd services
$ mvn package
# back to root keycloak dir
$ cd ..
```
* Upload to Nexus (from project root)
```
$ mvn -Pdistribution deploy
```
* Login to Nexus and release the maven repository uploads in the staging area.
* Upload src and distro zips to sf.net/projects/keycloak. This includes appliance, war-dist, each adapter, and proxy distros. You need to create an adapters folder on sf.net and each uploaded adapter there.
* Upload documentation to docs.jboss.org
```
$ sftp keycloak@filemgmt.jboss.org
> cd docs_htdocs/keycloak/docs
> mkdir 1.0.0.Final (or whatever version)
> quit
$ unzip distribution/examples-docs-zip/target/keycloak-examples-docs-dist.zip
$ cd docs
$ rsync -rv --protocol=28 * keycloak@filemgmt.jboss.org:/docs_htdocs/keycloak/docs/1.0.0.Final
```
* tag release
```
$ git tag -a -m "1.0.0.Final" 1.0.0.Final
$ git push --tags
```
## Update Bower
```
$ git clone https://github.com/keycloak/keycloak-js-bower
$ cp <keycloak.js from dist> dist/keycloak-js-bower
$ cp <keycloak.min.js from dist> dist/keycloak-js-bower
```
Edit bower.json and set version (include -beta -rc, but not -final). Create tag.
## Update OpenShift Cartridge
See https://github.com/keycloak/openshift-keycloak-cartridge for details
## Update Docker image
Instructions TBD
## Maven central
Releases are automatically synced to Maven central, but this can take up to one day
## Announce
* Update Magnolia site to link keycloak docs and announcements.
* Write a blog and email about release including links to download, migration guide, docs, and blurb about what's new

View file

@ -8,14 +8,10 @@ The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can
To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome`
Mongo
-----
Database
--------
The testsuite is executed with JPA model implementation with data saved in H2 database by default. To run testsuite with Mongo model, just add property `-Dkeycloak.realm.provider=mongo` when executing it.
This single property will cause that mongo will be used for realm-model, user-model and audit.
Note that this will automatically run embedded Mongo database on localhost/27018 and it will stop it after whole testsuite is finished.
So you don't need to have Mongo installed on your laptop to run mongo execution tests.
By default the testsuite uses an embedded H2 database to test with other databases see (Database Testing)[DatabaseTesting.md].
Test utils
==========

View file

@ -76,4 +76,4 @@ It should be added last to the `DefaultMongoUpdaterProvider#updates` array.
Testing database migration
--------------------------
Get the database from an old version of Keycloak that includes the demo applications. Start the server with this and test it.
Get the database from an old version of Keycloak that includes the demo applications. Start the server with this and test it.

View file

@ -14,6 +14,11 @@
<description/>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.iharder</groupId>
<artifactId>base64</artifactId>

View file

@ -0,0 +1,23 @@
package org.keycloak.models;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface KeycloakContext {
UriInfo getUri();
HttpHeaders getRequestHeaders();
RealmModel getRealm();
void setRealm(RealmModel realm);
ClientModel getClient();
void setClient(ClientModel client);
}

View file

@ -10,6 +10,8 @@ import java.util.Set;
*/
public interface KeycloakSession {
KeycloakContext getContext();
KeycloakTransactionManager getTransaction();
<T extends Provider> T getProvider(Class<T> clazz);

View file

@ -1,11 +1,15 @@
package org.keycloak.models;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -18,6 +22,7 @@ public class PasswordPolicy {
public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
public static final String INVALID_PASSWORD_REGEX_PATTERN = "invalidPasswordRegexPatternMessage";
public static final String INVALID_PASSWORD_HISTORY = "invalidPasswordHistoryMessage";
private List<Policy> policies;
private String policyString;
@ -67,10 +72,12 @@ public class PasswordPolicy {
} else if (name.equals(HashIterations.NAME)) {
list.add(new HashIterations(args));
} else if (name.equals(RegexPatterns.NAME)) {
for(String regexPattern : args) {
for (String regexPattern : args) {
Pattern.compile(regexPattern);
}
list.add(new RegexPatterns(args));
} else if (name.equals(PasswordHistory.NAME)) {
list.add(new PasswordHistory(args));
}
}
return list;
@ -92,9 +99,35 @@ public class PasswordPolicy {
return -1;
}
public Error validate(String username, String password) {
/**
*
* @return -1 if no expired passwords setting
*/
public int getExpiredPasswords() {
if (policies == null)
return -1;
for (Policy p : policies) {
Error error = p.validate(username, password);
if (p instanceof PasswordHistory) {
return ((PasswordHistory) p).passwordHistoryPolicyValue;
}
}
return -1;
}
public Error validate(UserModel user, String password) {
for (Policy p : policies) {
Error error = p.validate(user, password);
if (error != null) {
return error;
}
}
return null;
}
public Error validate(String user, String password) {
for (Policy p : policies) {
Error error = p.validate(user, password);
if (error != null) {
return error;
}
@ -103,7 +136,8 @@ public class PasswordPolicy {
}
private static interface Policy {
public Error validate(String username, String password);
public Error validate(UserModel user, String password);
public Error validate(String user, String password);
}
public static class Error {
@ -131,9 +165,15 @@ public class PasswordPolicy {
public HashIterations(String[] args) {
iterations = intArg(NAME, 1, args);
}
@Override
public Error validate(String username, String password) {
public Error validate(String user, String password) {
return null;
}
@Override
public Error validate(UserModel user, String password) {
return null;
}
}
@ -148,6 +188,11 @@ public class PasswordPolicy {
public Error validate(String username, String password) {
return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class Length implements Policy {
@ -157,11 +202,17 @@ public class PasswordPolicy {
public Length(String[] args) {
min = intArg(NAME, 8, args);
}
@Override
public Error validate(String username, String password) {
return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class Digits implements Policy {
@ -171,6 +222,7 @@ public class PasswordPolicy {
public Digits(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public Error validate(String username, String password) {
@ -182,6 +234,11 @@ public class PasswordPolicy {
}
return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class LowerCase implements Policy {
@ -191,7 +248,7 @@ public class PasswordPolicy {
public LowerCase(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public Error validate(String username, String password) {
int count = 0;
@ -202,6 +259,11 @@ public class PasswordPolicy {
}
return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min) : null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class UpperCase implements Policy {
@ -222,6 +284,11 @@ public class PasswordPolicy {
}
return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class SpecialChars implements Policy {
@ -231,7 +298,7 @@ public class PasswordPolicy {
public SpecialChars(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public Error validate(String username, String password) {
int count = 0;
@ -242,6 +309,11 @@ public class PasswordPolicy {
}
return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class RegexPatterns implements Policy {
@ -256,17 +328,96 @@ public class PasswordPolicy {
public Error validate(String username, String password) {
Pattern pattern = null;
Matcher matcher = null;
for(String regexPattern : regexPatterns) {
for (String regexPattern : regexPatterns) {
pattern = Pattern.compile(regexPattern);
matcher = pattern.matcher(password);
if (!matcher.matches()) {
return new Error(INVALID_PASSWORD_REGEX_PATTERN, (Object)regexPatterns);
return new Error(INVALID_PASSWORD_REGEX_PATTERN, (Object) regexPatterns);
}
}
return null;
}
@Override
public Error validate(UserModel user, String password) {
return validate(user.getUsername(), password);
}
}
private static class PasswordHistory implements Policy {
private static final String NAME = "passwordHistory";
private int passwordHistoryPolicyValue;
public PasswordHistory(String[] args) {
passwordHistoryPolicyValue = intArg(NAME, 3, args);
}
@Override
public Error validate(String user, String password) {
return null;
}
@Override
public Error validate(UserModel user, String password) {
if (passwordHistoryPolicyValue != -1) {
UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
if (cred != null) {
if(new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue(), cred.getHashIterations())) {
return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
}
}
List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
UserCredentialModel.PASSWORD_HISTORY);
for (UserCredentialValueModel credential : passwordExpiredCredentials) {
if (new Pbkdf2PasswordEncoder(credential.getSalt()).verify(password, credential.getValue(), credential.getHashIterations())) {
return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
}
}
}
return null;
}
private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
if (model.getType().equals(credType)) {
return model;
}
}
return null;
}
private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue,
String credType) {
List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
if (model.getType().equals(credType)) {
credentialModels.add(model);
}
}
Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
if (credentialModels.size() > expiredPasswordsPolicyValue) {
return credentialModels.subList(0, expiredPasswordsPolicyValue);
}
return credentialModels;
}
}
private static int intArg(String policy, int defaultValue, String... args) {
if (args == null || args.length == 0) {
return defaultValue;

View file

@ -8,6 +8,7 @@ import java.util.UUID;
*/
public class UserCredentialModel {
public static final String PASSWORD = "password";
public static final String PASSWORD_HISTORY = "password-history";
public static final String PASSWORD_TOKEN = "password-token";
// Secret is same as password but it is not hashed

View file

@ -12,6 +12,7 @@ public class UserCredentialValueModel {
private String device;
private byte[] salt;
private int hashIterations;
private long createdDate;
public String getType() {
return type;
@ -52,4 +53,13 @@ public class UserCredentialValueModel {
public void setHashIterations(int iterations) {
this.hashIterations = iterations;
}
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
}

View file

@ -323,7 +323,7 @@ public class UserFederationManager implements UserProvider {
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
if (realm.getPasswordPolicy() != null) {
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue());
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user, credential.getValue());
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
}
}

View file

@ -5,11 +5,23 @@ package org.keycloak.models.entities;
*/
public class CredentialEntity {
private String id;
private String type;
private String value;
private String device;
private byte[] salt;
private int hashIterations;
private long createdDate;
private UserEntity user;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
@ -50,4 +62,21 @@ public class CredentialEntity {
public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
}
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
}

View file

@ -64,7 +64,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private List<String> eventsListeners = new ArrayList<String>();
private List<String> enabledEventTypes = new ArrayList<String>();
private String adminAppId;
private String masterAdminClient;
private boolean internationalizationEnabled;
private List<String> supportedLocales = new ArrayList<String>();
@ -391,12 +391,12 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.enabledEventTypes = enabledEventTypes;
}
public String getAdminAppId() {
return adminAppId;
public String getMasterAdminClient() {
return masterAdminClient;
}
public void setAdminAppId(String adminAppId) {
this.adminAppId = adminAppId;
public void setMasterAdminClient(String masterAdminClient) {
this.masterAdminClient = masterAdminClient;
}
public List<UserFederationProviderEntity> getUserFederationProviders() {

View file

@ -66,7 +66,7 @@ public class RepresentationToModel {
if (rep.getFailureFactor() != null) newRealm.setFailureFactor(rep.getFailureFactor());
if (rep.isEventsEnabled() != null) newRealm.setEventsEnabled(rep.isEventsEnabled());
if (rep.getEventsExpiration() != null) newRealm.setEventsExpiration(rep.getEventsExpiration());
if (rep.getEventsListeners() != null) newRealm.setEventsListeners(new HashSet<String>(rep.getEventsListeners()));
if (rep.getEventsListeners() != null) newRealm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore());
@ -411,8 +411,8 @@ public class RepresentationToModel {
if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme());
if (rep.isEventsEnabled() != null) realm.setEventsEnabled(rep.isEventsEnabled());
if (rep.getEventsExpiration() != null) realm.setEventsExpiration(rep.getEventsExpiration());
if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<String>(rep.getEventsListeners()));
if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<String>(rep.getEnabledEventTypes()));
if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));

View file

@ -1,5 +1,9 @@
package org.keycloak.models;
import static org.junit.Assert.fail;
import java.util.regex.PatternSyntaxException;
import org.junit.Assert;
import org.junit.Test;
@ -79,6 +83,48 @@ public class PasswordPolicyTest {
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
}
@Test
public void testRegexPatterns() {
PasswordPolicy policy = null;
try {
policy = new PasswordPolicy("regexPatterns");
fail("Expected NullPointerEXception: Regex Pattern cannot be null.");
} catch (NullPointerException e) {
// Expected NPE as regex pattern is null.
}
try {
policy = new PasswordPolicy("regexPatterns(*)");
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
} catch (PatternSyntaxException e) {
// Expected PSE as regex pattern(or any of its token) is not quantifiable.
}
try {
policy = new PasswordPolicy("regexPatterns(*,**)");
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
} catch (PatternSyntaxException e) {
// Expected PSE as regex pattern(or any of its token) is not quantifiable.
}
//Fails to match one of the regex pattern
policy = new PasswordPolicy("regexPatterns(jdoe,j*d)");
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
////Fails to match all of the regex patterns
policy = new PasswordPolicy("regexPatterns(j*p,j*d,adoe)");
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
policy = new PasswordPolicy("regexPatterns([a-z][a-z][a-z][a-z][0-9])");
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
policy = new PasswordPolicy("regexPatterns(jdoe)");
Assert.assertNull(policy.validate("jdoe", "jdoe"));
policy = new PasswordPolicy("regexPatterns([a-z][a-z][a-z][a-z][0-9])");
Assert.assertNull(policy.validate("jdoe", "jdoe0"));
}
@Test
public void testComplex() {

View file

@ -966,14 +966,14 @@ public class RealmAdapter implements RealmModel {
@Override
public void setMasterAdminClient(ClientModel client) {
if (client == null) {
realm.setAdminAppId(null);
realm.setMasterAdminClient(null);
this.masterAdminApp = null;
} else {
String appId = client.getId();
if (appId == null) {
throw new IllegalStateException("Master Admin app not initialized.");
}
realm.setAdminAppId(appId);
realm.setMasterAdminClient(appId);
this.masterAdminApp = client;
}
}

View file

@ -16,7 +16,9 @@
*/
package org.keycloak.models.file.adapter;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientModel;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -28,11 +30,14 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.connections.file.InMemoryModel;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.entities.FederatedIdentityEntity;
@ -209,29 +214,80 @@ public class UserAdapter implements UserModel, Comparable {
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
updatePasswordCredential(cred);
} else {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
user.getCredentials().add(credentialEntity);
} else {
credentialEntity.setValue(cred.getValue());
}
}
}
private void updatePasswordCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setDevice(cred.getDevice());
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
user.getCredentials().add(credentialEntity);
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
byte[] salt = Pbkdf2PasswordEncoder.getSalt();
int hashIterations = 1;
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy != null) {
hashIterations = policy.getHashIterations();
if (hashIterations == -1) hashIterations = 1;
}
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
} else {
credentialEntity.setValue(cred.getValue());
int expiredPasswordsPolicyValue = -1;
PasswordPolicy policy = realm.getPasswordPolicy();
if(policy != null) {
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
}
if (expiredPasswordsPolicyValue != -1) {
user.getCredentials().remove(credentialEntity);
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
user.getCredentials().add(credentialEntity);
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
}
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
user.getCredentials().add(credentialEntity);
} else {
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities != null && credentialEntities.size() > 0) {
user.getCredentials().removeAll(credentialEntities);
}
setValue(credentialEntity, cred);
}
}
}
private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setCreatedDate(new Date().getTime());
credentialEntity.setDevice(cred.getDevice());
return credentialEntity;
}
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
byte[] salt = getSalt();
int hashIterations = 1;
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy != null) {
hashIterations = policy.getHashIterations();
if (hashIterations == -1)
hashIterations = 1;
}
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
}
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
@ -244,6 +300,30 @@ public class UserAdapter implements UserModel, Comparable {
return null;
}
private List<CredentialEntity> getCredentialEntities(UserEntity userEntity, String credType) {
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
credentialEntities.add(entity);
}
}
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
// Orders from most recent to least recent
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
return credentialEntities;
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
@ -253,6 +333,7 @@ public class UserAdapter implements UserModel, Comparable {
UserCredentialValueModel credModel = new UserCredentialValueModel();
credModel.setType(credEntity.getType());
credModel.setDevice(credEntity.getDevice());
credModel.setCreatedDate(credEntity.getCreatedDate());
credModel.setValue(credEntity.getValue());
credModel.setSalt(credEntity.getSalt());
credModel.setHashIterations(credEntity.getHashIterations());
@ -272,6 +353,7 @@ public class UserAdapter implements UserModel, Comparable {
// credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(credModel.getType());
// credentialEntity.setUser(user);
credModel.setCreatedDate(credModel.getCreatedDate());
user.getCredentials().add(credentialEntity);
}

View file

@ -3,7 +3,7 @@ package org.keycloak.models.cache.infinispan;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedApplication;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
@ -77,19 +77,19 @@ public class InfinispanRealmCache implements RealmCache {
}
@Override
public CachedApplication getApplication(String id) {
public CachedClient getApplication(String id) {
if (!enabled) return null;
return get(id, CachedApplication.class);
return get(id, CachedClient.class);
}
@Override
public void invalidateApplication(CachedApplication app) {
public void invalidateApplication(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
cache.remove(app.getId());
}
@Override
public void addCachedApplication(CachedApplication app) {
public void addCachedClient(CachedClient app) {
if (!enabled) return;
logger.tracev("Adding application {0}", app.getId());
cache.put(app.getId(), app);

View file

@ -6,7 +6,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedApplication;
import org.keycloak.models.cache.entities.CachedClient;
import java.util.HashMap;
import java.util.HashSet;
@ -24,9 +24,9 @@ public class ClientAdapter implements ClientModel {
protected RealmCache cache;
protected ClientModel updated;
protected CachedApplication cached;
protected CachedClient cached;
public ClientAdapter(RealmModel cachedRealm, CachedApplication cached, CacheRealmProvider cacheSession, RealmCache cache) {
public ClientAdapter(RealmModel cachedRealm, CachedClient cached, CacheRealmProvider cacheSession, RealmCache cache) {
this.cachedRealm = cachedRealm;
this.cache = cache;
this.cacheSession = cacheSession;

View file

@ -6,8 +6,8 @@ import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedApplication;
import org.keycloak.models.cache.entities.CachedApplicationRole;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.entities.CachedRole;
@ -235,7 +235,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (roleInvalidations.contains(id)) return model;
if (model.getContainer() instanceof ClientModel) {
cached = new CachedApplicationRole(((ClientModel) model.getContainer()).getId(), model, realm);
cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
} else {
cached = new CachedRealmRole(model, realm);
}
@ -254,7 +254,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public ClientModel getClientById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getClientById(id, realm);
CachedApplication cached = cache.getApplication(id);
CachedClient cached = cache.getApplication(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
@ -263,8 +263,8 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
cached = new CachedApplication(cache, getDelegate(), realm, model);
cache.addCachedApplication(cached);
cached = new CachedClient(cache, getDelegate(), realm, model);
cache.addCachedClient(cached);
} else if (appInvalidations.contains(id)) {
return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {

View file

@ -1,6 +1,6 @@
package org.keycloak.models.cache;
import org.keycloak.models.cache.entities.CachedApplication;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
@ -14,7 +14,7 @@ public class MemoryRealmCache implements RealmCache {
protected ConcurrentHashMap<String, CachedRealm> realmCache = new ConcurrentHashMap<String, CachedRealm>();
protected ConcurrentHashMap<String, CachedRealm> realmCacheByName = new ConcurrentHashMap<String, CachedRealm>();
protected ConcurrentHashMap<String, CachedApplication> applicationCache = new ConcurrentHashMap<String, CachedApplication>();
protected ConcurrentHashMap<String, CachedClient> applicationCache = new ConcurrentHashMap<String, CachedClient>();
protected ConcurrentHashMap<String, CachedRole> roleCache = new ConcurrentHashMap<String, CachedRole>();
protected volatile boolean enabled = true;
@ -72,18 +72,18 @@ public class MemoryRealmCache implements RealmCache {
}
@Override
public CachedApplication getApplication(String id) {
public CachedClient getApplication(String id) {
if (!enabled) return null;
return applicationCache.get(id);
}
@Override
public void invalidateApplication(CachedApplication app) {
public void invalidateApplication(CachedClient app) {
applicationCache.remove(app.getId());
}
@Override
public void addCachedApplication(CachedApplication app) {
public void addCachedClient(CachedClient app) {
if (!enabled) return;
applicationCache.put(app.getId(), app);
}

View file

@ -474,7 +474,7 @@ public class RealmAdapter implements RealmModel {
public Map<String, ClientModel> getClientNameMap() {
if (updated != null) return updated.getClientNameMap();
Map<String, ClientModel> map = new HashMap<String, ClientModel>();
for (String id : cached.getApplications().values()) {
for (String id : cached.getClients().values()) {
ClientModel model = cacheSession.getClientById(id, this);
if (model == null) {
throw new IllegalStateException("Cached application not found: " + id);
@ -488,7 +488,7 @@ public class RealmAdapter implements RealmModel {
public List<ClientModel> getClients() {
if (updated != null) return updated.getClients();
List<ClientModel> apps = new LinkedList<ClientModel>();
for (String id : cached.getApplications().values()) {
for (String id : cached.getClients().values()) {
ClientModel model = cacheSession.getClientById(id, this);
if (model == null) {
throw new IllegalStateException("Cached application not found: " + id);
@ -531,7 +531,7 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel getClientByClientId(String clientId) {
if (updated != null) return updated.getClientByClientId(clientId);
String id = cached.getApplications().get(clientId);
String id = cached.getClients().get(clientId);
if (id == null) return null;
return getClientById(id);
}
@ -752,7 +752,7 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel getMasterAdminClient() {
return cacheSession.getRealm(Config.getAdminRealm()).getClientById(cached.getMasterAdminApp());
return cacheSession.getRealm(Config.getAdminRealm()).getClientById(cached.getMasterAdminClient());
}
@Override

View file

@ -1,6 +1,6 @@
package org.keycloak.models.cache;
import org.keycloak.models.cache.entities.CachedApplication;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
@ -21,11 +21,11 @@ public interface RealmCache {
void invalidateCachedRealmById(String id);
CachedApplication getApplication(String id);
CachedClient getApplication(String id);
void invalidateApplication(CachedApplication app);
void invalidateApplication(CachedClient app);
void addCachedApplication(CachedApplication app);
void addCachedClient(CachedClient app);
void invalidateCachedApplicationById(String id);

View file

@ -3,7 +3,7 @@ package org.keycloak.models.cache;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedApplicationRole;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.entities.CachedRole;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -106,8 +106,8 @@ public class RoleAdapter implements RoleModel {
if (cached instanceof CachedRealmRole) {
return realm;
} else {
CachedApplicationRole appRole = (CachedApplicationRole)cached;
return realm.getClientById(appRole.getAppId());
CachedClientRole appRole = (CachedClientRole)cached;
return realm.getClientById(appRole.getIdClient());
}
}

View file

@ -1,22 +0,0 @@
package org.keycloak.models.cache.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CachedApplicationRole extends CachedRole {
private final String appId;
public CachedApplicationRole(String appId, RoleModel model, RealmModel realm) {
super(model, realm);
this.appId = appId;
}
public String getAppId() {
return appId;
}
}

View file

@ -21,7 +21,7 @@ import java.util.TreeMap;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CachedApplication {
public class CachedClient {
private String id;
private String name;
private String realm;
@ -49,7 +49,7 @@ public class CachedApplication {
private int nodeReRegistrationTimeout;
private Map<String, Integer> registeredNodes;
public CachedApplication(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
id = model.getId();
secret = model.getSecret();
name = model.getClientId();
@ -79,7 +79,7 @@ public class CachedApplication {
consentRequired = model.isConsentRequired();
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
cache.addCachedRole(new CachedApplicationRole(id, role, realm));
cache.addCachedRole(new CachedClientRole(id, role, realm));
}
nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();

View file

@ -0,0 +1,22 @@
package org.keycloak.models.cache.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CachedClientRole extends CachedRole {
private final String idClient;
public CachedClientRole(String idClient, RoleModel model, RealmModel realm) {
super(model, realm);
this.idClient = idClient;
}
public String getIdClient() {
return idClient;
}
}

View file

@ -66,7 +66,7 @@ public class CachedRealm {
private String accountTheme;
private String adminTheme;
private String emailTheme;
private String masterAdminApp;
private String masterAdminClient;
private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
@ -81,7 +81,6 @@ public class CachedRealm {
private Set<String> enabledEventTypes = new HashSet<String>();
private List<String> defaultRoles = new LinkedList<String>();
private Map<String, String> realmRoles = new HashMap<String, String>();
private Map<String, String> applications = new HashMap<String, String>();
private Map<String, String> clients = new HashMap<String, String>();
private boolean internationalizationEnabled;
private Set<String> supportedLocales = new HashSet<String>();
@ -155,7 +154,7 @@ public class CachedRealm {
eventsListeners.addAll(model.getEventsListeners());
enabledEventTypes.addAll(model.getEnabledEventTypes());
defaultRoles.addAll(model.getDefaultRoles());
masterAdminApp = model.getMasterAdminClient().getId();
masterAdminClient = model.getMasterAdminClient().getId();
for (RoleModel role : model.getRoles()) {
realmRoles.put(role.getName(), role.getId());
@ -163,10 +162,10 @@ public class CachedRealm {
cache.addCachedRole(cachedRole);
}
for (ClientModel app : model.getClients()) {
applications.put(app.getClientId(), app.getId());
CachedApplication cachedApp = new CachedApplication(cache, delegate, model, app);
cache.addCachedApplication(cachedApp);
for (ClientModel client : model.getClients()) {
clients.put(client.getClientId(), client.getId());
CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
cache.addCachedClient(cachedClient);
}
internationalizationEnabled = model.isInternationalizationEnabled();
@ -180,8 +179,8 @@ public class CachedRealm {
return id;
}
public String getMasterAdminApp() {
return masterAdminApp;
public String getMasterAdminClient() {
return masterAdminClient;
}
public String getName() {
@ -196,10 +195,6 @@ public class CachedRealm {
return realmRoles;
}
public Map<String, String> getApplications() {
return applications;
}
public Map<String, String> getClients() {
return clients;
}

View file

@ -545,9 +545,9 @@ public class ClientAdapter implements ClientModel {
@Override
public RoleModel getRole(String name) {
TypedQuery<RoleEntity> query = em.createNamedQuery("getAppRoleByName", RoleEntity.class);
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoleByName", RoleEntity.class);
query.setParameter("name", name);
query.setParameter("application", entity);
query.setParameter("client", entity);
List<RoleEntity> roles = query.getResultList();
if (roles.size() == 0) return null;
return new RoleAdapter(realm, em, roles.get(0));
@ -563,8 +563,8 @@ public class ClientAdapter implements ClientModel {
RoleEntity roleEntity = new RoleEntity();
roleEntity.setId(id);
roleEntity.setName(name);
roleEntity.setApplication(entity);
roleEntity.setApplicationRole(true);
roleEntity.setClient(entity);
roleEntity.setClientRole(true);
roleEntity.setRealmId(realm.getId());
em.persist(roleEntity);
entity.getRoles().add(roleEntity);
@ -581,13 +581,13 @@ public class ClientAdapter implements ClientModel {
session.users().preRemove(getRealm(), roleModel);
RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
if (!role.isApplicationRole()) return false;
if (!role.isClientRole()) return false;
entity.getRoles().remove(role);
entity.getDefaultRoles().remove(role);
em.createNativeQuery("delete from COMPOSITE_ROLE where CHILD_ROLE = :role").setParameter("role", role).executeUpdate();
em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", role).executeUpdate();
role.setApplication(null);
role.setClient(null);
em.flush();
em.remove(role);
em.flush();

View file

@ -91,7 +91,7 @@ public class JpaRealmProvider implements RealmProvider {
RealmAdapter adapter = new RealmAdapter(session, em, realm);
session.users().preRemove(adapter);
for (ClientEntity a : new LinkedList<>(realm.getApplications())) {
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
adapter.removeClient(a.getId());
}

View file

@ -619,8 +619,8 @@ public class RealmAdapter implements RealmModel {
@Override
public List<ClientModel> getClients() {
List<ClientModel> list = new ArrayList<ClientModel>();
if (realm.getApplications() == null) return list;
for (ClientEntity entity : realm.getApplications()) {
if (realm.getClients() == null) return list;
for (ClientEntity entity : realm.getClients()) {
list.add(new ClientAdapter(this, em, session, entity));
}
return list;
@ -633,15 +633,15 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel addClient(String id, String clientId) {
ClientEntity applicationData = new ClientEntity();
applicationData.setId(id);
applicationData.setClientId(clientId);
applicationData.setEnabled(true);
applicationData.setRealm(realm);
realm.getApplications().add(applicationData);
em.persist(applicationData);
ClientEntity entity = new ClientEntity();
entity.setId(id);
entity.setClientId(clientId);
entity.setEnabled(true);
entity.setRealm(realm);
realm.getClients().add(entity);
em.persist(entity);
em.flush();
final ClientModel resource = new ClientAdapter(this, em, session, applicationData);
final ClientModel resource = new ClientAdapter(this, em, session, entity);
em.flush();
session.getKeycloakSessionFactory().publish(new ClientCreationEvent() {
@Override
@ -655,15 +655,15 @@ public class RealmAdapter implements RealmModel {
@Override
public boolean removeClient(String id) {
if (id == null) return false;
ClientModel application = getClientById(id);
if (application == null) return false;
ClientModel client = getClientById(id);
if (client == null) return false;
for (RoleModel role : application.getRoles()) {
application.removeRole(role);
for (RoleModel role : client.getRoles()) {
client.removeRole(role);
}
ClientEntity clientEntity = null;
Iterator<ClientEntity> it = realm.getApplications().iterator();
Iterator<ClientEntity> it = realm.getClients().iterator();
while (it.hasNext()) {
ClientEntity ae = it.next();
if (ae.getId().equals(id)) {
@ -672,12 +672,12 @@ public class RealmAdapter implements RealmModel {
break;
}
}
for (ClientEntity a : realm.getApplications()) {
for (ClientEntity a : realm.getClients()) {
if (a.getId().equals(id)) {
clientEntity = a;
}
}
if (application == null) {
if (client == null) {
return false;
}
em.remove(clientEntity);
@ -1066,13 +1066,13 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel getMasterAdminClient() {
return new ClientAdapter(this, em, session, realm.getMasterAdminApp());
return new ClientAdapter(this, em, session, realm.getMasterAdminClient());
}
@Override
public void setMasterAdminClient(ClientModel client) {
ClientEntity appEntity = client !=null ? em.getReference(ClientEntity.class, client.getId()) : null;
realm.setMasterAdminApp(appEntity);
realm.setMasterAdminClient(appEntity);
em.flush();
}

View file

@ -104,8 +104,8 @@ public class RoleAdapter implements RoleModel {
@Override
public RoleContainerModel getContainer() {
if (role.isApplicationRole()) {
return realm.getClientById(role.getApplication().getId());
if (role.isClientRole()) {
return realm.getClientById(role.getClient().getId());
} else {
return realm;

View file

@ -18,7 +18,11 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -160,7 +164,6 @@ public class UserAdapter implements UserModel {
}
}
@Override
public String getFirstName() {
return user.getFirstName();
@ -208,33 +211,86 @@ public class UserAdapter implements UserModel {
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
updatePasswordCredential(cred);
} else {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
} else {
credentialEntity.setValue(cred.getValue());
}
}
em.flush();
}
private void updatePasswordCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(cred.getType());
credentialEntity.setDevice(cred.getDevice());
credentialEntity.setUser(user);
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
byte[] salt = getSalt();
int hashIterations = 1;
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy != null) {
hashIterations = policy.getHashIterations();
if (hashIterations == -1) hashIterations = 1;
}
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
} else {
credentialEntity.setValue(cred.getValue());
int expiredPasswordsPolicyValue = -1;
PasswordPolicy policy = realm.getPasswordPolicy();
if(policy != null) {
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
}
if (expiredPasswordsPolicyValue != -1) {
user.getCredentials().remove(credentialEntity);
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
user.getCredentials().add(credentialEntity);
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
}
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);
} else {
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities != null && credentialEntities.size() > 0) {
user.getCredentials().removeAll(credentialEntities);
}
setValue(credentialEntity, cred);
}
}
}
private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(cred.getType());
credentialEntity.setCreatedDate(new Date().getTime());
credentialEntity.setDevice(cred.getDevice());
em.flush();
credentialEntity.setUser(user);
return credentialEntity;
}
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
byte[] salt = getSalt();
int hashIterations = 1;
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy != null) {
hashIterations = policy.getHashIterations();
if (hashIterations == -1)
hashIterations = 1;
}
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
}
private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) {
@ -247,6 +303,30 @@ public class UserAdapter implements UserModel {
return null;
}
private List<CredentialEntity> getCredentialEntities(UserEntity userEntity, String credType) {
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
credentialEntities.add(entity);
}
}
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
// Orders from most recent to least recent
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
return credentialEntities;
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
@ -258,6 +338,7 @@ public class UserAdapter implements UserModel {
credModel.setType(credEntity.getType());
credModel.setDevice(credEntity.getDevice());
credModel.setValue(credEntity.getValue());
credModel.setCreatedDate(credEntity.getCreatedDate());
credModel.setSalt(credEntity.getSalt());
credModel.setHashIterations(credEntity.getHashIterations());
@ -276,6 +357,7 @@ public class UserAdapter implements UserModel {
credentialEntity = new CredentialEntity();
credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(credModel.getType());
credentialEntity.setCreatedDate(credModel.getCreatedDate());
credentialEntity.setUser(user);
em.persist(credentialEntity);
user.getCredentials().add(credentialEntity);

View file

@ -96,17 +96,17 @@ public class ClientEntity {
@Column(name="NODE_REREG_TIMEOUT")
private int nodeReRegistrationTimeout;
@OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "application")
@OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "client")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="APPLICATION_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="APPLICATION_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
@ElementCollection
@MapKeyColumn(name="NAME")
@Column(name="VALUE")
@CollectionTable(name="APP_NODE_REGISTRATIONS", joinColumns={ @JoinColumn(name="APPLICATION_ID") })
@CollectionTable(name="CLIENT_NODE_REGISTRATIONS", joinColumns={ @JoinColumn(name="CLIENT_ID") })
Map<String, Integer> registeredNodes = new HashMap<String, Integer>();
public RealmEntity getRealm() {

View file

@ -37,7 +37,9 @@ public class CredentialEntity {
protected byte[] salt;
@Column(name="HASH_ITERATIONS")
protected int hashIterations;
@Column(name="CREATED_DATE")
protected long createdDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
protected UserEntity user;
@ -97,4 +99,13 @@ public class CredentialEntity {
public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
}
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
}

View file

@ -104,8 +104,8 @@ public class RealmEntity {
List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="REALM_APPLICATION", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="APPLICATION_ID") })
Collection<ClientEntity> applications = new ArrayList<ClientEntity>();
@JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
Collection<ClientEntity> clients = new ArrayList<>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@ -136,8 +136,8 @@ public class RealmEntity {
protected Set<String> enabledEventTypes = new HashSet<String>();
@OneToOne
@JoinColumn(name="MASTER_ADMIN_APP")
protected ClientEntity masterAdminApp;
@JoinColumn(name="MASTER_ADMIN_CLIENT")
protected ClientEntity masterAdminClient;
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
protected List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
@ -318,12 +318,12 @@ public class RealmEntity {
this.requiredCredentials = requiredCredentials;
}
public Collection<ClientEntity> getApplications() {
return applications;
public Collection<ClientEntity> getClients() {
return clients;
}
public void setApplications(Collection<ClientEntity> applications) {
this.applications = applications;
public void setClients(Collection<ClientEntity> clients) {
this.clients = clients;
}
public Collection<RoleEntity> getRoles() {
@ -437,12 +437,12 @@ public class RealmEntity {
this.enabledEventTypes = enabledEventTypes;
}
public ClientEntity getMasterAdminApp() {
return masterAdminApp;
public ClientEntity getMasterAdminClient() {
return masterAdminClient;
}
public void setMasterAdminApp(ClientEntity masterAdminApp) {
this.masterAdminApp = masterAdminApp;
public void setMasterAdminClient(ClientEntity masterAdminClient) {
this.masterAdminClient = masterAdminClient;
}
public List<UserFederationProviderEntity> getUserFederationProviders() {

View file

@ -21,11 +21,11 @@ import java.util.Collection;
*/
@Entity
@Table(name="KEYCLOAK_ROLE", uniqueConstraints = {
@UniqueConstraint(columnNames = { "NAME", "APP_REALM_CONSTRAINT" })
@UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
})
@NamedQueries({
@NamedQuery(name="getAppRoleByName", query="select role from RoleEntity role where role.name = :name and role.application = :application"),
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.applicationRole = false and role.name = :name and role.realm = :realm")
@NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
})
public class RoleEntity {
@ -46,16 +46,16 @@ public class RoleEntity {
@JoinColumn(name = "REALM")
private RealmEntity realm;
@Column(name="APPLICATION_ROLE")
private boolean applicationRole;
@Column(name="CLIENT_ROLE")
private boolean clientRole;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "APPLICATION")
private ClientEntity application;
@JoinColumn(name = "CLIENT")
private ClientEntity client;
// Hack to ensure that either name+application or name+realm are unique. Needed due to MS-SQL as it don't allow multiple NULL values in the column, which is part of constraint
@Column(name="APP_REALM_CONSTRAINT", length = 36)
private String appRealmConstraint;
// Hack to ensure that either name+client or name+realm are unique. Needed due to MS-SQL as it don't allow multiple NULL values in the column, which is part of constraint
@Column(name="CLIENT_REALM_CONSTRAINT", length = 36)
private String clientRealmConstraint;
@ManyToMany(fetch = FetchType.LAZY, cascade = {})
@JoinTable(name = "COMPOSITE_ROLE", joinColumns = @JoinColumn(name = "COMPOSITE"), inverseJoinColumns = @JoinColumn(name = "CHILD_ROLE"))
@ -101,12 +101,12 @@ public class RoleEntity {
this.compositeRoles = compositeRoles;
}
public boolean isApplicationRole() {
return applicationRole;
public boolean isClientRole() {
return clientRole;
}
public void setApplicationRole(boolean applicationRole) {
this.applicationRole = applicationRole;
public void setClientRole(boolean clientRole) {
this.clientRole = clientRole;
}
public RealmEntity getRealm() {
@ -115,26 +115,26 @@ public class RoleEntity {
public void setRealm(RealmEntity realm) {
this.realm = realm;
this.appRealmConstraint = realm.getId();
this.clientRealmConstraint = realm.getId();
}
public ClientEntity getApplication() {
return application;
public ClientEntity getClient() {
return client;
}
public void setApplication(ClientEntity application) {
this.application = application;
if (application != null) {
this.appRealmConstraint = application.getId();
public void setClient(ClientEntity client) {
this.client = client;
if (client != null) {
this.clientRealmConstraint = client.getId();
}
}
public String getAppRealmConstraint() {
return appRealmConstraint;
public String getClientRealmConstraint() {
return clientRealmConstraint;
}
public void setAppRealmConstraint(String appRealmConstraint) {
this.appRealmConstraint = appRealmConstraint;
public void setClientRealmConstraint(String clientRealmConstraint) {
this.clientRealmConstraint = clientRealmConstraint;
}
@Override

View file

@ -91,7 +91,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public SslRequired getSslRequired() {
return SslRequired.valueOf(realm.getSslRequired());
return realm.getSslRequired() != null ? SslRequired.valueOf(realm.getSslRequired()) : null;
}
@Override
@ -604,11 +604,11 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<MongoClientEntity> appDatas = getMongoStore().loadEntities(MongoClientEntity.class, query, invocationContext);
List<MongoClientEntity> clientEntities = getMongoStore().loadEntities(MongoClientEntity.class, query, invocationContext);
List<ClientModel> result = new ArrayList<ClientModel>();
for (MongoClientEntity appData : appDatas) {
result.add(new ClientAdapter(session, this, appData, invocationContext));
for (MongoClientEntity clientEntity : clientEntities) {
result.add(new ClientAdapter(session, this, clientEntity, invocationContext));
}
return result;
}
@ -620,14 +620,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ClientModel addClient(String id, String clientId) {
MongoClientEntity appData = new MongoClientEntity();
appData.setId(id);
appData.setClientId(clientId);
appData.setRealmId(getId());
appData.setEnabled(true);
getMongoStore().insertEntity(appData, invocationContext);
MongoClientEntity clientEntity = new MongoClientEntity();
clientEntity.setId(id);
clientEntity.setClientId(clientId);
clientEntity.setRealmId(getId());
clientEntity.setEnabled(true);
getMongoStore().insertEntity(clientEntity, invocationContext);
final ClientModel model = new ClientAdapter(session, this, appData, invocationContext);
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
session.getKeycloakSessionFactory().publish(new ClientCreationEvent() {
@Override
public ClientModel getCreatedClient() {
@ -979,14 +979,14 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ClientModel getMasterAdminClient() {
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getAdminAppId(), invocationContext);
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
return appData != null ? new ClientAdapter(session, this, appData, invocationContext) : null;
}
@Override
public void setMasterAdminClient(ClientModel client) {
String adminAppId = client != null ? client.getId() : null;
realm.setAdminAppId(adminAppId);
realm.setMasterAdminClient(adminAppId);
updateRealm();
}

View file

@ -1,5 +1,7 @@
package org.keycloak.models.mongo.keycloak.adapters;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@ -17,6 +19,8 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -29,7 +33,7 @@ import java.util.Set;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implements UserModel {
private final MongoUserEntity user;
private final RealmModel realm;
private final KeycloakSession session;
@ -177,31 +181,81 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override
public void updateCredential(UserCredentialModel cred) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
updatePasswordCredential(cred);
} else {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = setCredentials(user, cred);
credentialEntity.setValue(cred.getValue());
user.getCredentials().add(credentialEntity);
} else {
credentialEntity.setValue(cred.getValue());
}
}
getMongoStore().updateEntity(user, invocationContext);
}
private void updatePasswordCredential(UserCredentialModel cred) {
CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType());
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setDevice(cred.getDevice());
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
user.getCredentials().add(credentialEntity);
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
byte[] salt = Pbkdf2PasswordEncoder.getSalt();
int hashIterations = 1;
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy != null) {
hashIterations = policy.getHashIterations();
if (hashIterations == -1) hashIterations = 1;
}
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
} else {
credentialEntity.setValue(cred.getValue());
}
credentialEntity.setDevice(cred.getDevice());
getMongoStore().updateEntity(user, invocationContext);
int expiredPasswordsPolicyValue = -1;
PasswordPolicy policy = realm.getPasswordPolicy();
if(policy != null) {
expiredPasswordsPolicyValue = policy.getExpiredPasswords();
}
if (expiredPasswordsPolicyValue != -1) {
user.getCredentials().remove(credentialEntity);
credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY);
user.getCredentials().add(credentialEntity);
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) {
user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size()));
}
credentialEntity = setCredentials(user, cred);
setValue(credentialEntity, cred);
user.getCredentials().add(credentialEntity);
} else {
List<CredentialEntity> credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY);
if (credentialEntities != null && credentialEntities.size() > 0) {
user.getCredentials().removeAll(credentialEntities);
}
setValue(credentialEntity, cred);
}
}
}
private CredentialEntity setCredentials(MongoUserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setCreatedDate(new Date().getTime());
credentialEntity.setDevice(cred.getDevice());
return credentialEntity;
}
private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) {
byte[] salt = getSalt();
int hashIterations = 1;
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy != null) {
hashIterations = policy.getHashIterations();
if (hashIterations == -1)
hashIterations = 1;
}
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
}
private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) {
@ -214,6 +268,30 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
return null;
}
private List<CredentialEntity> getCredentialEntities(MongoUserEntity userEntity, String credType) {
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
for (CredentialEntity entity : userEntity.getCredentials()) {
if (entity.getType().equals(credType)) {
credentialEntities.add(entity);
}
}
// Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow
// Orders from most recent to least recent
Collections.sort(credentialEntities, new Comparator<CredentialEntity>() {
public int compare(CredentialEntity credFirst, CredentialEntity credSecond) {
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
return -1;
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
return 1;
} else {
return 0;
}
}
});
return credentialEntities;
}
@Override
public List<UserCredentialValueModel> getCredentialsDirectly() {
List<CredentialEntity> credentials = user.getCredentials();
@ -222,6 +300,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
UserCredentialValueModel credModel = new UserCredentialValueModel();
credModel.setType(credEntity.getType());
credModel.setDevice(credEntity.getDevice());
credModel.setCreatedDate(credEntity.getCreatedDate());
credModel.setValue(credEntity.getValue());
credModel.setSalt(credEntity.getSalt());
credModel.setHashIterations(credEntity.getHashIterations());
@ -239,6 +318,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
if (credentialEntity == null) {
credentialEntity = new CredentialEntity();
credentialEntity.setType(credModel.getType());
credModel.setCreatedDate(credModel.getCreatedDate());
user.getCredentials().add(credentialEntity);
}

View file

@ -17,8 +17,10 @@ import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.ConditionsType;
import org.keycloak.dom.saml.v2.assertion.SubjectConfirmationDataType;
import org.keycloak.dom.saml.v2.assertion.AudienceRestrictionType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.w3c.dom.Document;
import java.net.URI;
import static org.keycloak.saml.common.util.StringUtil.isNotNull;
@ -156,6 +158,11 @@ public class SAML2LoginResponseBuilder {
AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
//Add request issuer as the audience restriction
AudienceRestrictionType audience = new AudienceRestrictionType();
audience.addAudience(URI.create(requestIssuer));
assertion.getConditions().addCondition(audience);
//Update Conditions NotOnOrAfter
if(assertionExpiration > 0) {
ConditionsType conditions = assertion.getConditions();

View file

@ -30,7 +30,7 @@ import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.ErrorPage;
import org.w3c.dom.Document;
import javax.ws.rs.core.HttpHeaders;
@ -152,7 +152,7 @@ public class SamlProtocol implements LoginProtocol {
return builder.redirectBinding().response();
}
} catch (Exception e) {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE );
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
}
@ -309,7 +309,7 @@ public class SamlProtocol implements LoginProtocol {
samlDocument = builder.buildDocument(samlModel);
} catch (Exception e) {
logger.error("failed", e);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.FAILED_TO_PROCESS_RESPONSE);
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
SAML2BindingBuilder2 bindingBuilder = new SAML2BindingBuilder2();
@ -331,7 +331,7 @@ public class SamlProtocol implements LoginProtocol {
publicKey = SamlProtocolUtils.getEncryptionValidationKey(client);
} catch (Exception e) {
logger.error("failed", e);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE);
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
bindingBuilder.encrypt(publicKey);
}
@ -343,7 +343,7 @@ public class SamlProtocol implements LoginProtocol {
}
} catch (Exception e) {
logger.error("failed", e);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE );
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
}

View file

@ -23,7 +23,7 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.HttpAuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.ErrorPage;
import org.keycloak.util.StreamUtil;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
@ -102,18 +102,18 @@ public class SamlService {
if (!checkSsl()) {
event.event(EventType.LOGIN);
event.error(Errors.SSL_REQUIRED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED );
return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
}
if (!realm.isEnabled()) {
event.event(EventType.LOGIN_ERROR);
event.error(Errors.REALM_DISABLED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
}
if (samlRequest == null && samlResponse == null) {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
return null;
@ -127,7 +127,7 @@ public class SamlService {
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
event.detail(Details.REASON, "invalid_destination");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
@ -135,7 +135,7 @@ public class SamlService {
logger.warn("Unknown saml response.");
event.event(EventType.LOGOUT);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
// assume this is a logout response
UserSessionModel userSession = authResult.getSession();
@ -144,7 +144,7 @@ public class SamlService {
logger.warn("UserSession is not tagged as logging out.");
event.event(EventType.LOGOUT);
event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
logger.debug("logout response");
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
@ -157,7 +157,7 @@ public class SamlService {
if (documentHolder == null) {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
SAML2Object samlObject = documentHolder.getSamlObject();
@ -169,32 +169,34 @@ public class SamlService {
if (client == null) {
event.event(EventType.LOGIN);
event.error(Errors.CLIENT_NOT_FOUND);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
}
if (!client.isEnabled()) {
event.event(EventType.LOGIN);
event.error(Errors.CLIENT_DISABLED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
}
if ((client instanceof ClientModel) && ((ClientModel)client).isBearerOnly()) {
event.event(EventType.LOGIN);
event.error(Errors.NOT_ALLOWED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.BEARER_ONLY);
return ErrorPage.error(session, Messages.BEARER_ONLY);
}
if (client.isDirectGrantsOnly()) {
event.event(EventType.LOGIN);
event.error(Errors.NOT_ALLOWED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY );
return ErrorPage.error(session, Messages.DIRECT_GRANTS_ONLY);
}
session.getContext().setClient(client);
try {
verifySignature(documentHolder, client);
} catch (VerificationException e) {
SamlService.logger.error("request validation failed", e);
event.event(EventType.LOGIN);
event.error(Errors.INVALID_SIGNATURE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
}
logger.debug("verified request");
if (samlObject instanceof AuthnRequestType) {
@ -212,7 +214,7 @@ public class SamlService {
} else {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
}
@ -226,7 +228,7 @@ public class SamlService {
if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
event.detail(Details.REASON, "invalid_destination");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
String bindingType = getBindingType(requestAbstractType);
if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) bindingType = SamlProtocol.SAML_POST_BINDING;
@ -248,7 +250,7 @@ public class SamlService {
if (redirect == null) {
event.error(Errors.INVALID_REDIRECT_URI);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI );
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
}
@ -271,7 +273,7 @@ public class SamlService {
} else {
event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
event.detail(Details.REASON, "unsupported_nameid_format");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNSUPPORTED_NAME_ID_FORMAT);
return ErrorPage.error(session, Messages.UNSUPPORTED_NAME_ID_FORMAT);
}
}
@ -283,7 +285,7 @@ public class SamlService {
HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers);
if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
// Attach state from SPNEGO authentication
@ -335,7 +337,7 @@ public class SamlService {
if (!uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) {
event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
event.detail(Details.REASON, "invalid_destination");
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
@ -374,7 +376,7 @@ public class SamlService {
if (redirectUri != null) {
redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
if (redirectUri == null) {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI );
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
}
}
if (redirectUri != null) {

View file

@ -14,6 +14,13 @@
}
},
"eventsListener": {
"jboss-logging" : {
"success-level": "debug",
"error-level": "warn"
}
},
"realm": {
"provider": "jpa"
},

View file

@ -18,7 +18,6 @@ import org.keycloak.protocol.oidc.endpoints.ValidateTokenEndpoint;
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Flows;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@ -204,7 +203,7 @@ public class OIDCLoginProtocolService {
@Path("oauth/oob")
@GET
public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo, headers);
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class);
if (code != null) {
return forms.setClientSessionCode(code).createCode();
} else {

View file

@ -1,18 +1,17 @@
package org.keycloak.protocol.oidc;
import org.keycloak.OAuth2Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import org.keycloak.wellknown.WellKnownProvider;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -29,8 +28,17 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query");
private KeycloakSession session;
public OIDCWellKnownProvider(KeycloakSession session) {
this.session = session;
}
@Override
public Object getConfig(RealmModel realm, UriInfo uriInfo) {
public Object getConfig() {
UriInfo uriInfo = session.getContext().getUri();
RealmModel realm = session.getContext().getRealm();
UriBuilder uriBuilder = RealmsResource.protocolUrl(uriInfo);
OIDCConfigurationRepresentation config = new OIDCConfigurationRepresentation();

View file

@ -11,16 +11,13 @@ import org.keycloak.wellknown.WellKnownProviderFactory;
*/
public class OIDCWellKnownProviderFactory implements WellKnownProviderFactory {
private WellKnownProvider provider;
@Override
public WellKnownProvider create(KeycloakSession session) {
return provider;
return new OIDCWellKnownProvider(session);
}
@Override
public void init(Config.Scope config) {
provider = new OIDCWellKnownProvider();
}
@Override
@ -29,7 +26,6 @@ public class OIDCWellKnownProviderFactory implements WellKnownProviderFactory {
@Override
public void close() {
provider = null;
}
@Override

View file

@ -25,8 +25,7 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.HttpAuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
@ -116,7 +115,7 @@ public class AuthorizationEndpoint {
action = Action.REGISTER;
if (!realm.isRegistrationAllowed()) {
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
throw new ErrorPageException(session, Messages.REGISTRATION_NOT_ALLOWED);
}
return this;
@ -148,21 +147,21 @@ public class AuthorizationEndpoint {
private void checkSsl() {
if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
event.error(Errors.SSL_REQUIRED);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
throw new ErrorPageException(session, Messages.HTTPS_REQUIRED);
}
}
private void checkRealm() {
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
throw new ErrorPageException(session, Messages.REALM_NOT_ENABLED);
}
}
private void checkClient() {
if (clientId == null) {
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM );
throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM );
}
event.client(clientId);
@ -170,18 +169,20 @@ public class AuthorizationEndpoint {
client = realm.getClientByClientId(clientId);
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.CLIENT_NOT_FOUND );
throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND );
}
if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.BEARER_ONLY );
throw new ErrorPageException(session, Messages.BEARER_ONLY );
}
if (client.isDirectGrantsOnly()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY);
throw new ErrorPageException(session, Messages.DIRECT_GRANTS_ONLY);
}
session.getContext().setClient(client);
}
private void checkResponseType() {
@ -190,7 +191,7 @@ public class AuthorizationEndpoint {
responseType = legacyResponseType;
} else {
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
}
}
@ -200,7 +201,7 @@ public class AuthorizationEndpoint {
action = Action.CODE;
} else {
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
}
}
@ -210,7 +211,7 @@ public class AuthorizationEndpoint {
redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client);
if (redirectUri == null) {
event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
}
}
@ -238,7 +239,7 @@ public class AuthorizationEndpoint {
IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
if (identityProviderModel == null) {
return Flows.forms(session, realm, null, uriInfo, headers)
return session.getProvider(LoginFormsProvider.class)
.setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
.createErrorPage();
}
@ -272,14 +273,13 @@ public class AuthorizationEndpoint {
return buildRedirectToIdentityProvider(identityProviders.get(0).getAlias(), accessCode);
}
return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.IDENTITY_PROVIDER_NOT_UNIQUE, realm.getName()).createErrorPage();
return session.getProvider(LoginFormsProvider.class).setError(Messages.IDENTITY_PROVIDER_NOT_UNIQUE, realm.getName()).createErrorPage();
}
return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.REALM_SUPPORTS_NO_CREDENTIALS, realm.getName()).createErrorPage();
return session.getProvider(LoginFormsProvider.class).setError(Messages.REALM_SUPPORTS_NO_CREDENTIALS, realm.getName()).createErrorPage();
}
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
.setClientSessionCode(accessCode);
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode);
// Attach state from SPNEGO authentication
if (httpAuthOutput.getChallenge() != null) {
@ -307,7 +307,7 @@ public class AuthorizationEndpoint {
private Response buildRegister() {
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
return Flows.forms(session, realm, client, uriInfo, headers)
return session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
.createRegistration();
}

View file

@ -24,7 +24,7 @@ import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.ErrorPage;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@ -92,7 +92,7 @@ public class LogoutEndpoint {
event.event(EventType.LOGOUT);
event.detail(Details.REDIRECT_URI, redirect);
event.error(Errors.INVALID_REDIRECT_URI);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
}
redirect = validatedUri;
}
@ -112,7 +112,7 @@ public class LogoutEndpoint {
if (error) {
event.event(EventType.LOGOUT);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
}

View file

@ -27,7 +27,7 @@ import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;

View file

@ -36,7 +36,7 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;

View file

@ -15,10 +15,9 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.*;

View file

@ -4,7 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.Urls;
import javax.ws.rs.core.UriInfo;
import java.net.URI;

View file

@ -0,0 +1,50 @@
package org.keycloak.services;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultKeycloakContext implements KeycloakContext {
private RealmModel realm;
private ClientModel client;
@Override
public UriInfo getUri() {
return ResteasyProviderFactory.getContextData(UriInfo.class);
}
@Override
public HttpHeaders getRequestHeaders() {
return ResteasyProviderFactory.getContextData(HttpHeaders.class);
}
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
}
@Override
public ClientModel getClient() {
return client;
}
@Override
public void setClient(ClientModel client) {
this.client = client;
}
}

View file

@ -1,5 +1,8 @@
package org.keycloak.services;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
@ -12,6 +15,7 @@ import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import javax.ws.rs.core.UriInfo;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@ -32,11 +36,18 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserProvider userModel;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
private KeycloakContext context;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
this.transactionManager = new DefaultKeycloakTransactionManager();
federationManager = new UserFederationManager(this);
context = new DefaultKeycloakContext();
}
@Override
public KeycloakContext getContext() {
return context;
}
private RealmProvider getRealmProvider() {

View file

@ -19,35 +19,20 @@
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.services.resources.flows;
package org.keycloak.services;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class Flows {
public class ErrorPage {
private Flows() {
}
public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo, HttpHeaders headers) {
return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client).setHttpHeaders(headers);
}
public static ErrorFlows errors() {
return new ErrorFlows();
}
public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String message, Object ... parameters) {
return Flows.forms(session, realm, null, uriInfo, headers).setError(message,parameters).createErrorPage();
public static Response error(KeycloakSession session, String message, Object... parameters) {
return session.getProvider(LoginFormsProvider.class).setError(message, parameters).createErrorPage();
}

Some files were not shown because too many files have changed in this diff Show more