Compare commits

..
Author SHA1 Message Date
Stian Thorgersen f402fb1a55 Version bump 2015-05-20 07:13:14 +02:00
Stian Thorgersen dc64260c31 NotSerializableException: org.keycloak.models.sessions.infinispan.entities.LoginFailureKey 2015-05-20 07:12:07 +02:00
Stian Thorgersen d26712a1d4 Version bump 2015-01-28 14:23:05 +01:00
Stian Thorgersen db8935444f Fix federation provider example to load users file from it's own ClassLoader instead of Thread CL 2015-01-28 13:23:00 +01:00
Stian Thorgersen 5b769ed829 Updates to migration guide 2015-01-28 12:03:59 +01:00
Stian Thorgersen 7585b5ab49 Added provider section to docs 2015-01-28 11:25:11 +01:00
Stian Thorgersen 95287b2faa Removed details on using deployment overrides, this will need reviewing and adding to 1.2 instead 2015-01-28 10:48:07 +01:00
Stian Thorgersen d707133742 Update Travis CI config 2015-01-28 09:25:03 +01:00
Stian Thorgersen c0e0391a87 Add travis.yml 2015-01-28 09:25:03 +01:00
Stian Thorgersen 7139c683cc KEYCLOAK-999 Load providers from file-system 2015-01-28 09:25:01 +01:00
mposolda 19939068d5 Fixed test failures caused by uncleared Time offset 2015-01-26 17:20:05 +01:00
mposolda 9ffe8287a7 Improve OOTB experience for ssh and jmx authentication in fuse 2015-01-23 20:40:38 +01:00
girirajsharma be74476de1 Updated KeyCloak relative URI as per scheme, host and port of request. 2015-01-21 17:15:34 +01:00
girirajsharma 45c5fa28fc Fixed KeyCloak relative URI optimization with auth-server-url in docbook 2015-01-21 17:15:25 +01:00
mposolda a4bed6e433 Added docs and example for SSH and JMX authentication on fuse 2015-01-21 13:36:00 +01:00
Stian Thorgersen 575943fbb4 KEYCLOAK-980 Password reset is possible with expired link 2015-01-21 13:10:43 +01:00
Stian Thorgersen 46fa593d4a KEYCLOAK-983 Fix login after reset-password 2015-01-21 12:26:58 +01:00
mposolda 9819d9997e Have fuse example working on newest fuse 6.2. Refactoring of ServletReregistrationService to work on fuse 6.1, 6.2 and karaf 3.0.2 2015-01-20 21:42:36 +01:00
Stian Thorgersen cdb0d6b5bf KEYCLOAK-977 Use reflection to find constructor for JBossGenericPrincipal to support EAP 6.4 2015-01-20 13:25:33 +01:00
mposolda ae86028e6e KEYCLOAK-981 Module org.picketlink.federation.bindings doesn't work with EAP6 2015-01-20 11:13:39 +01:00
mposolda 0cfe4c694a Fix SAML examples. Fix links in README files for SAML examples 2015-01-20 11:13:10 +01:00
Stian Thorgersen 034735cd64 KEYCLOAK-975 Client/app secret should be read-only, not disabled 2015-01-19 14:07:55 +01:00
mposolda 7567fea6c1 Update instructions for fuse demo 2015-01-19 13:21:06 +01:00
Stian Thorgersen 3af087e36f Fix OAuthRedirectUriTest 2015-01-19 12:21:26 +01:00
mposolda 45e04944a4 Remove references about installing auth-server on JBoss AS 7.1.1 2015-01-16 17:59:33 +01:00
mposolda 0706e6f778 Make it easier to run demo on different host then auth-server 2015-01-16 17:39:37 +01:00
Stian Thorgersen 655aa6194e Fix OAuthRedirectUriTest 2015-01-16 13:06:27 +01:00
mposolda 862baf2d5b KEYCLOAK-974 ServerVersionResource doesn't handle cors requests 2015-01-16 12:25:13 +01:00
Stian Thorgersen fc148207ce KEYCLOAK-967 Username is displayed as undefined in Cordova example 2015-01-16 11:48:23 +01:00
Stian Thorgersen 095561cb29 KEYCLOAK-964 JS Console has two tabs for Show ID Token 2015-01-16 11:27:52 +01:00
Stian Thorgersen 9bd3f43ce5 KEYCLOAK-965 NPE in TokenManager.verifyAccess if application is deleted 2015-01-16 11:25:24 +01:00
Stian Thorgersen 4ea49a14ea KEYCLOAK-962 Changed access token request to use redirect_uri from initial request instead of the resolved redirect_uri 2015-01-16 11:01:53 +01:00
Stian Thorgersen 12c155cd9c KEYCLOAK-963 Change code output to single-line for 'urn:ietf:wg:oauth:2.0:oob' 2015-01-16 11:01:48 +01:00
mposolda 0249adbacc Typo in packaging 2015-01-15 19:52:29 +01:00
mposolda d69b01ad3c KEYCLOAK-853 Documentation for login modules 2015-01-15 18:58:40 +01:00
mposolda e7dbfecdb0 Documentation for fuse adapter and example 2015-01-15 18:58:40 +01:00
Stian Thorgersen 37b396c106 KEYCLOAK-960 Fix pagination of app sessions 2015-01-15 14:03:29 +01:00
Marek Posolda b03dddccf4 Merge pull request #917 from mposolda/fuse2
KEYCLOAK-539 Fuse adapter. OSGI bundling. OSGI headers in keycloak adapt...
2015-01-15 13:24:27 +01:00
mposolda 1b7e019042 KEYCLOAK-539 Fuse adapter. OSGI bundling. OSGI headers in keycloak adapter maven artifacts. Rename package in jetty-core 2015-01-15 13:11:30 +01:00
Stian Thorgersen 33c47c8441 KEYCLOAK-958 Support role names with '/' and '\' 2015-01-15 12:21:58 +01:00
Stian Thorgersen 8e5325b5d5 KEYCLOAK-959 Warning message in server log after logging out all sessions 2015-01-15 09:54:52 +01:00
Stian Thorgersen e37c6f262b Add redirect_uri to access token request in keycloak.js 2015-01-14 15:17:22 +01:00
Stian Thorgersen 5bebd31b0c KEYCLOAK-955 Fix AeroGear project integration example 2015-01-14 14:31:39 +01:00
Stian Thorgersen cc76dcf58b KEYCLOAK-949 Disabled user with wrong credentials receive wrong error message 2015-01-14 10:10:36 +01:00
Stian Thorgersen d0b614fdcf KEYCLOAK-957 Access Token Request does not verify redirect_uri 2015-01-14 09:59:04 +01:00
Stian Thorgersen c325b9f04d Merge pull request #914 from gerbermichi/email
Passwort reset dialog shows an error message if the user has an invalid email
2015-01-14 09:15:26 +01:00
girirajsharma c311f47d5d Minor changes : Fixed spell/grammatical errors in docbook. 2015-01-14 09:13:28 +01:00
Michael Gerber aa95ff6d84 check if email is null 2015-01-12 20:33:08 +01:00
1470 changed files with 10646 additions and 305046 deletions

No files matched your search

+1 -4
View File
@@ -9,7 +9,7 @@ It can be used for social applications as well as enterprise applications. It i
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.
* Social Broker. Enable Google, Facebook, Yahoo, Twitter 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.
@@ -31,9 +31,6 @@ Here's some of the features:
* 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.
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
and video tutorials.
-53
View File
@@ -1,53 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-broker-core</artifactId>
<name>Keycloak Broker Core</name>
<description/>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
@@ -1,62 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
/**
* @author Pedro Igor
*/
public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> implements IdentityProvider<C> {
private final C config;
public AbstractIdentityProvider(C config) {
this.config = config;
}
public C getConfig() {
return this.config;
}
@Override
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
return Response.noContent().build();
}
@Override
public void close() {
// no-op
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
return null;
}
@Override
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
return null;
}
}
@@ -1,57 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @author Pedro Igor
*/
public abstract class AbstractIdentityProviderFactory<T extends IdentityProvider> implements IdentityProviderFactory<T> {
@Override
public void close() {
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public T create(KeycloakSession session) {
return null;
}
@Override
public Map<String, String> parseConfig(InputStream inputStream) {
return new HashMap<String, String>();
}
}
@@ -1,83 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import javax.ws.rs.core.UriInfo;
/**
* @author Pedro Igor
*/
public class AuthenticationRequest {
private final KeycloakSession session;
private final UriInfo uriInfo;
private final String state;
private final HttpRequest httpRequest;
private final RealmModel realm;
private final String redirectUri;
private final ClientSessionModel clientSession;
public AuthenticationRequest(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
this.session = session;
this.realm = realm;
this.httpRequest = httpRequest;
this.uriInfo = uriInfo;
this.state = state;
this.redirectUri = redirectUri;
this.clientSession = clientSession;
}
public KeycloakSession getSession() {
return session;
}
public UriInfo getUriInfo() {
return this.uriInfo;
}
public String getState() {
return this.state;
}
public HttpRequest getHttpRequest() {
return this.httpRequest;
}
public RealmModel getRealm() {
return this.realm;
}
/**
* <p>Returns the redirect url that must be included in an authentication request in order to process responses from an
* identity provider.</p>
*
* @return
*/
public String getRedirectUri() {
return this.redirectUri;
}
public ClientSessionModel getClientSession() {
return this.clientSession;
}
}
@@ -1,135 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
/**
* <p>Represents all identity information obtained from an {@link IdentityProvider} after a
* successful authentication.</p>
*
* @author Pedro Igor
*/
public class FederatedIdentity {
private String id;
private String username;
private String firstName;
private String lastName;
private String email;
private String token;
private String identityProviderId;
private String brokerSessionId;
private String brokerUserId;
public FederatedIdentity(String id) {
if (id == null) {
throw new RuntimeException("No identifier provider for identity.");
}
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setName(String name) {
if (name != null) {
int i = name.lastIndexOf(' ');
if (i != -1) {
firstName = name.substring(0, i);
lastName = name.substring(i + 1);
} else {
firstName = name;
}
}
}
public String getLastName() {
return lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void setToken(String token) {
this.token = token;
}
public String getToken() {
return this.token;
}
public String getIdentityProviderId() {
return this.identityProviderId;
}
public void setIdentityProviderId(String identityProviderId) {
this.identityProviderId = identityProviderId;
}
public String getBrokerSessionId() {
return brokerSessionId;
}
public void setBrokerSessionId(String brokerSessionId) {
this.brokerSessionId = brokerSessionId;
}
public String getBrokerUserId() {
return brokerUserId;
}
public void setBrokerUserId(String brokerUserId) {
this.brokerUserId = brokerUserId;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", username='" + username + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", token='" + token + '\'' +
", identityProviderId='" + identityProviderId + '\'' +
'}';
}
}
@@ -1,32 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
/**
* @author pedroigor
*/
public class IdentityBrokerException extends RuntimeException {
public IdentityBrokerException(String message) {
super(message);
}
public IdentityBrokerException(String message, Throwable t) {
super(message, t);
}
}
@@ -1,100 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.provider.Provider;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Map;
/**
* @author Pedro Igor
*/
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
public interface AuthenticationCallback {
/**
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
* with the remote IDP
*
* @param userNotes notes to add to the UserSessionModel
* @param identityProviderConfig provider config
* @param federatedIdentity federated identity
* @param code relayState or state parameter used to identity the client session
* @return
*/
public Response authenticated(Map<String, String> userNotes, IdentityProviderModel identityProviderConfig, FederatedIdentity federatedIdentity, String code);
}
/**
* JAXRS callback endpoint for when the remote IDP wants to callback to keycloak.
*
* @return
*/
Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event);
/**
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
* only once during the authentication.</p>
*
* <p>Depending on how the authentication is performed, this method may redirect the user to the identity provider for authentication.
* In this case, the response would contain a {@link javax.ws.rs.core.Response} that will be used to redirect the user.</p>
*
* <p>However, if the authentication flow does not require a redirect to the identity provider (eg.: simple challenge/response mechanism), this method may return a response containing
* a {@link FederatedIdentity} representing the identity information for an user. In this case, the authentication flow stops.</p>
*
* @param request The initial authentication request. Contains all the contextual information in order to build an authentication request to the
* identity provider.
* @return
*/
Response handleRequest(AuthenticationRequest request);
/**
* <p>Returns a {@link javax.ws.rs.core.Response} containing the token previously stored during the authentication process for a
* specific user.</p>
*
* @param identity
* @return
*/
Response retrieveToken(FederatedIdentityModel identity);
/**
* Called when a Keycloak application initiates a logout through the browser. This is expected to do a logout
* with the IDP
*
* @param userSession
* @param uriInfo
* @param realm
* @return null if this is not supported by this provider
*/
Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
/**
* Export a representation of the IdentityProvider in a specific format. For example, a SAML EntityDescriptor
*
* @return
*/
Response export(UriInfo uriInfo, RealmModel realm, String format);
}
@@ -1,56 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.provider.ProviderFactory;
import java.io.InputStream;
import java.util.Map;
/**
* @author Pedro Igor
*/
public interface IdentityProviderFactory<T extends IdentityProvider> extends ProviderFactory<T> {
/**
* <p>A friendly name for this factory.</p>
*
* @return
*/
String getName();
/**
* <p>Creates an {@link IdentityProvider} based on the configuration contained in
* <code>model</code>.</p>
*
* @param model The configuration to be used to create the identity provider.
* @return
*/
T create(IdentityProviderModel model);
/**
* <p>Creates an {@link IdentityProvider} based on the configuration from
* <code>inputStream</code>.</p>
*
* @param model The model containing the common abd basic configuration for an identity provider.
* @param inputStream The input stream from where configuration will be loaded from..
* @return
*/
Map<String, String> parseConfig(InputStream inputStream);
}
@@ -1,45 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.provider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author Pedro Igor
*/
public class IdentityProviderSpi implements Spi {
public static final String IDENTITY_PROVIDER_SPI_NAME = "identity_provider";
@Override
public String getName() {
return IDENTITY_PROVIDER_SPI_NAME;
}
@Override
public Class<? extends Provider> getProviderClass() {
return IdentityProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return IdentityProviderFactory.class;
}
}
@@ -1 +0,0 @@
org.keycloak.broker.provider.IdentityProviderSpi
-57
View File
@@ -1,57 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-broker-oidc</artifactId>
<name>Keycloak Broker - OpenID Connect Identity Provider</name>
<description/>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,233 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.oidc;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.jboss.logging.Logger;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Pedro Igor
*/
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> {
protected static final Logger logger = Logger.getLogger(AbstractOAuth2IdentityProvider.class);
public static final String OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
public static final String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
public static final String FEDERATED_REFRESH_TOKEN = "FEDERATED_REFRESH_TOKEN";
public static final String FEDERATED_TOKEN_EXPIRATION = "FEDERATED_TOKEN_EXPIRATION";
protected static ObjectMapper mapper = new ObjectMapper();
public static final String OAUTH2_PARAMETER_ACCESS_TOKEN = "access_token";
public static final String OAUTH2_PARAMETER_SCOPE = "scope";
public static final String OAUTH2_PARAMETER_STATE = "state";
public static final String OAUTH2_PARAMETER_RESPONSE_TYPE = "response_type";
public static final String OAUTH2_PARAMETER_REDIRECT_URI = "redirect_uri";
public static final String OAUTH2_PARAMETER_CODE = "code";
public static final String OAUTH2_PARAMETER_CLIENT_ID = "client_id";
public static final String OAUTH2_PARAMETER_CLIENT_SECRET = "client_secret";
public static final String OAUTH2_PARAMETER_GRANT_TYPE = "grant_type";
public AbstractOAuth2IdentityProvider(C config) {
super(config);
if (config.getDefaultScope() == null || config.getDefaultScope().isEmpty()) {
config.setDefaultScope(getDefaultScopes());
}
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
return new Endpoint(callback, realm, event);
}
@Override
public Response handleRequest(AuthenticationRequest request) {
try {
URI authorizationUrl = createAuthorizationUrl(request).build();
return Response.temporaryRedirect(authorizationUrl).build();
} catch (Exception e) {
throw new IdentityBrokerException("Could not create authentication request.", e);
}
}
@Override
public Response retrieveToken(FederatedIdentityModel identity) {
return Response.ok(identity.getToken()).build();
}
@Override
public C getConfig() {
return super.getConfig();
}
protected String extractTokenFromResponse(String response, String tokenName) {
if (response.startsWith("{")) {
try {
return mapper.readTree(response).get(tokenName).getTextValue();
} catch (IOException e) {
throw new IdentityBrokerException("Could not extract token [" + tokenName + "] from response [" + response + "].", e);
}
} else {
Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
if (matcher.find()) {
return matcher.group(1);
}
}
return null;
}
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
if (accessToken == null) {
throw new IdentityBrokerException("No access token from server.");
}
return doGetFederatedIdentity(accessToken);
}
protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
return null;
}
;
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
.queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
.queryParam(OAUTH2_PARAMETER_STATE, request.getState())
.queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
.queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
}
protected String getJsonProperty(JsonNode jsonNode, String name) {
if (jsonNode.has(name)) {
return jsonNode.get(name).asText();
}
return null;
}
protected JsonNode asJsonNode(String json) throws IOException {
return mapper.readTree(json);
}
protected abstract String getDefaultScopes();
protected class Endpoint {
protected AuthenticationCallback callback;
protected RealmModel realm;
protected EventBuilder event;
@Context
protected KeycloakSession session;
@Context
protected ClientConnection clientConnection;
@Context
protected HttpHeaders headers;
@Context
protected UriInfo uriInfo;
public Endpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
this.callback = callback;
this.realm = realm;
this.event = event;
}
@GET
public Response authResponse(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_STATE) String state,
@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE) String authorizationCode,
@QueryParam(OAuth2Constants.ERROR) String error) {
if (error != null) {
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
event.event(EventType.LOGIN);
event.error(error);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
try {
if (authorizationCode != null) {
String response = generateTokenRequest(authorizationCode).asString();
HashMap<String, String> userNotes = new HashMap<String, String>();
FederatedIdentity federatedIdentity = getFederatedIdentity(userNotes, response);
if (getConfig().isStoreToken()) {
federatedIdentity.setToken(response);
}
return callback.authenticated(userNotes, getConfig(), federatedIdentity, state);
}
} catch (Exception e) {
logger.error("Failed to make identity provider oauth callback", e);
}
event.event(EventType.LOGIN);
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
public SimpleHttp generateTokenRequest(String authorizationCode) {
return SimpleHttp.doPost(getConfig().getTokenUrl())
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
}
}
}
@@ -1,100 +0,0 @@
package org.keycloak.broker.oidc;
import org.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.PemUtils;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.security.PublicKey;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
super(config);
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
return new KeycloakEndpoint(callback, realm, event);
}
protected class KeycloakEndpoint extends OIDCEndpoint {
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
super(callback, realm, event);
}
@POST
@Path(AdapterConstants.K_LOGOUT)
public Response backchannelLogout(String input) {
JWSInput token = new JWSInput(input);
PublicKey key = getExternalIdpKey();
if (key != null) {
if (!verify(token, key)) {
logger.warn("Failed to verify logout request");
return Response.status(400).build();
}
}
LogoutAction action = null;
try {
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (!validateAction(action)) return Response.status(400).build();
if (action.getKeycloakSessionIds() != null) {
for (String sessionId : action.getKeycloakSessionIds()) {
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
if (userSession != null) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
}
}
}
return Response.ok().build();
}
protected boolean validateAction(AdminAction action) {
if (!action.validate()) {
logger.warn("admin request failed, not validated" + action.getAction());
return false;
}
if (action.isExpired()) {
logger.warn("admin request failed, expired token");
return false;
}
if (!getConfig().getClientId().equals(action.getResource())) {
logger.warn("Resource name does not match");
return false;
}
return true;
}
@Override
public SimpleHttp generateTokenRequest(String authorizationCode) {
return super.generateTokenRequest(authorizationCode)
.param(AdapterConstants.APPLICATION_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
}
}
}
@@ -1,56 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.oidc;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<KeycloakOIDCIdentityProvider> {
public static final String PROVIDER_ID = "keycloak-oidc";
@Override
public String getName() {
return "Keycloak OpenID Connect";
}
@Override
public KeycloakOIDCIdentityProvider create(IdentityProviderModel model) {
return new KeycloakOIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public Map<String, String> parseConfig(InputStream inputStream) {
return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
}
}
@@ -1,78 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.oidc;
import org.keycloak.models.IdentityProviderModel;
/**
* @author Pedro Igor
*/
public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
public OAuth2IdentityProviderConfig(IdentityProviderModel model) {
super(model);
}
public String getAuthorizationUrl() {
return getConfig().get("authorizationUrl");
}
public void setAuthorizationUrl(String authorizationUrl) {
getConfig().put("authorizationUrl", authorizationUrl);
}
public String getTokenUrl() {
return getConfig().get("tokenUrl");
}
public void setTokenUrl(String tokenUrl) {
getConfig().put("tokenUrl", tokenUrl);
}
public String getUserInfoUrl() {
return getConfig().get("userInfoUrl");
}
public void setUserInfoUrl(String userInfoUrl) {
getConfig().put("userInfoUrl", userInfoUrl);
}
public String getClientId() {
return getConfig().get("clientId");
}
public void setClientId(String clientId) {
getConfig().put("clientId", clientId);
}
public String getClientSecret() {
return getConfig().get("clientSecret");
}
public void setClientSecret(String clientSecret) {
getConfig().put("clientSecret", clientSecret);
}
public String getDefaultScope() {
return getConfig().get("defaultScope");
}
public void setDefaultScope(String defaultScope) {
getConfig().put("defaultScope", defaultScope);
}
}
@@ -1,280 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.oidc;
import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
import org.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
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;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.security.PublicKey;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> {
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
public static final String SCOPE_OPENID = "openid";
public static final String FEDERATED_ID_TOKEN = "FEDERATED_ID_TOKEN";
public OIDCIdentityProvider(OIDCIdentityProviderConfig config) {
super(config);
String defaultScope = config.getDefaultScope();
if (!defaultScope.contains(SCOPE_OPENID)) {
config.setDefaultScope(SCOPE_OPENID + " " + defaultScope);
}
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
return new OIDCEndpoint(callback, realm, event);
}
protected PublicKey getExternalIdpKey() {
String signingCert = getConfig().getCertificateSignatureVerifier();
try {
if (signingCert != null && !signingCert.trim().equals("")) {
return PemUtils.decodeCertificate(signingCert).getPublicKey();
} else if (getConfig().getPublicKeySignatureVerifier() != null && !getConfig().getPublicKeySignatureVerifier().trim().equals("")) {
return PemUtils.decodePublicKey(getConfig().getPublicKeySignatureVerifier());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
protected boolean verify(JWSInput jws, PublicKey key) {
if (key == null) return true;
if (!getConfig().isValidateSignature()) return true;
return RSAProvider.verify(jws, key);
}
protected class OIDCEndpoint extends Endpoint {
public OIDCEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
super(callback, realm, event);
}
@GET
@Path("logout_response")
public Response logoutResponse(@Context UriInfo uriInfo,
@QueryParam("state") String state) {
UserSessionModel userSession = session.sessions().getUserSession(realm, state);
if (userSession == null) {
logger.error("no valid user session");
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, 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 AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
}
}
@Override
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
.queryParam("state", userSession.getId());
String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
String redirect = RealmsResource.brokerUrl(uriInfo)
.path(IdentityBrokerService.class, "getEndpoint")
.path(OIDCEndpoint.class, "logoutResponse")
.build(realm.getName(), getConfig().getAlias()).toString();
logoutUri.queryParam("post_logout_redirect_uri", redirect);
return Response.status(302).location(logoutUri.build()).build();
}
@Override
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
UriBuilder authorizationUrl = super.createAuthorizationUrl(request);
String prompt = getConfig().getPrompt();
if (prompt != null && !prompt.isEmpty()) {
authorizationUrl.queryParam(OAUTH2_PARAMETER_PROMPT, prompt);
}
return authorizationUrl;
}
@Override
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
AccessTokenResponse tokenResponse = null;
try {
tokenResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
} catch (IOException e) {
throw new IdentityBrokerException("Could not decode access token response.", e);
}
PublicKey key = getExternalIdpKey();
String accessToken = verifyAccessToken(key, tokenResponse);
String encodedIdToken = tokenResponse.getIdToken();
notes.put(FEDERATED_ACCESS_TOKEN, accessToken);
notes.put(FEDERATED_ID_TOKEN, encodedIdToken);
notes.put(FEDERATED_REFRESH_TOKEN, tokenResponse.getRefreshToken());
notes.put(FEDERATED_TOKEN_EXPIRATION, Long.toString(tokenResponse.getExpiresIn()));
IDToken idToken = validateIdToken(key, encodedIdToken);
try {
String id = idToken.getSubject();
String name = idToken.getName();
String preferredUsername = idToken.getPreferredUsername();
String email = idToken.getEmail();
if (id == null || name == null || preferredUsername == null || email == null && getConfig().getUserInfoUrl() != null) {
JsonNode userInfo = SimpleHttp.doGet(getConfig().getUserInfoUrl())
.header("Authorization", "Bearer " + accessToken)
.asJson();
id = getJsonProperty(userInfo, "sub");
name = getJsonProperty(userInfo, "name");
preferredUsername = getJsonProperty(userInfo, "preferred_username");
email = getJsonProperty(userInfo, "email");
}
FederatedIdentity identity = new FederatedIdentity(id);
identity.setId(id);
identity.setName(name);
identity.setEmail(email);
identity.setBrokerUserId(getConfig().getAlias() + "." + id);
if (tokenResponse.getSessionState() != null) {
identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
}
if (preferredUsername == null) {
preferredUsername = email;
}
if (preferredUsername == null) {
preferredUsername = id;
}
identity.setUsername(preferredUsername);
if (getConfig().isStoreToken()) {
identity.setToken(response);
}
return identity;
} catch (Exception e) {
throw new IdentityBrokerException("Could not fetch attributes from userinfo endpoint.", e);
}
}
private String verifyAccessToken(PublicKey key, AccessTokenResponse tokenResponse) {
String accessToken = tokenResponse.getToken();
if (accessToken == null) {
throw new IdentityBrokerException("No access_token from server.");
}
return accessToken;
}
private IDToken validateIdToken(PublicKey key, String encodedToken) {
if (encodedToken == null) {
throw new IdentityBrokerException("No id_token from server.");
}
try {
JWSInput jws = new JWSInput(encodedToken);
if (!verify(jws, key)) {
throw new IdentityBrokerException("IDToken signature validation failed");
}
IDToken idToken = jws.readJsonContent(IDToken.class);
String aud = idToken.getAudience();
String iss = idToken.getIssuer();
if (aud != null && !aud.equals(getConfig().getClientId())) {
throw new IdentityBrokerException("Wrong audience from id_token..");
}
String trustedIssuers = getConfig().getIssuer();
if (trustedIssuers != null) {
String[] issuers = trustedIssuers.split(",");
for (String trustedIssuer : issuers) {
if (iss != null && iss.equals(trustedIssuer.trim())) {
return idToken;
}
}
throw new IdentityBrokerException("Wrong issuer from id_token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
return idToken;
} catch (IOException e) {
throw new IdentityBrokerException("Could not decode id token.", e);
}
}
@Override
protected String getDefaultScopes() {
return "openid";
}
}
@@ -1,76 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.oidc;
import org.keycloak.models.IdentityProviderModel;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
public OIDCIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
super(identityProviderModel);
}
public String getPrompt() {
return getConfig().get("prompt");
}
public void setPrompt(String prompt) {
getConfig().put("prompt", prompt);
}
public String getIssuer() {
return getConfig().get("issuer");
}
public void setIssuer(String issuer) {
getConfig().put("issuer", issuer);
}
public String getLogoutUrl() {
return getConfig().get("logoutUrl");
}
public void setLogoutUrl(String url) {
getConfig().put("logoutUrl", url);
}
public String getCertificateSignatureVerifier() {
return getConfig().get("certificateSignatureVerifier");
}
public void setCertificateSignatureVerifier(String signingCertificate) {
getConfig().put("certificateSignatureVerifier", signingCertificate);
}
public String getPublicKeySignatureVerifier() {
return getConfig().get("publicKeySignatureVerifier");
}
public void setPublicKeySignatureVerifier(String signingCertificate) {
getConfig().put("publicKeySignatureVerifier", signingCertificate);
}
public boolean isValidateSignature() {
return Boolean.valueOf(getConfig().get("validateSignature"));
}
public void setValidateSignature(boolean validateSignature) {
getConfig().put("validateSignature", String.valueOf(validateSignature));
}
}
@@ -1,100 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.oidc;
import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKParser;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.PemUtils;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<OIDCIdentityProvider> {
public static final String PROVIDER_ID = "oidc";
@Override
public String getName() {
return "OpenID Connect v1.0";
}
@Override
public OIDCIdentityProvider create(IdentityProviderModel model) {
return new OIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public Map<String, String> parseConfig(InputStream inputStream) {
return parseOIDCConfig(inputStream);
}
protected static Map<String, String> parseOIDCConfig(InputStream inputStream) {
OIDCConfigurationRepresentation rep = null;
try {
rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
} catch (IOException e) {
throw new RuntimeException("failed to load openid connect metadata", e);
}
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel());
config.setIssuer(rep.getIssuer());
config.setLogoutUrl(rep.getLogoutEndpoint());
config.setAuthorizationUrl(rep.getAuthorizationEndpoint());
config.setTokenUrl(rep.getTokenEndpoint());
config.setUserInfoUrl(rep.getUserinfoEndpoint());
if (rep.getJwksUri() != null) {
String uri = rep.getJwksUri();
String keySetString = null;
try {
keySetString = SimpleHttp.doGet(uri).asString();
JSONWebKeySet keySet = JsonSerialization.readValue(keySetString, JSONWebKeySet.class);
for (JWK jwk : keySet.getKeys()) {
JWKParser parse = JWKParser.create(jwk);
if (parse.getJwk().getPublicKeyUse().equals(JWK.SIG_USE)) {
PublicKey key = parse.toPublicKey();
config.setPublicKeySignatureVerifier(KeycloakModelUtils.getPemFromKey(key));
config.setValidateSignature(true);
break;
}
}
} catch (IOException e) {
throw new RuntimeException("F ailed to query JWKSet from: " + uri, e);
}
}
return config.getConfig();
}
}
@@ -1,2 +0,0 @@
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
-23
View File
@@ -1,23 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-broker-parent</artifactId>
<name>Keycloak Broker Parent</name>
<description/>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>oidc</module>
<module>saml</module>
</modules>
</project>
-53
View File
@@ -1,53 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-broker-saml</artifactId>
<name>Keycloak Broker - SAML Identity Provider</name>
<description/>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-protocol</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-federation</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -1,458 +0,0 @@
package org.keycloak.broker.saml;
import org.jboss.logging.Logger;
import org.keycloak.ClientConnection;
import org.keycloak.VerificationException;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.saml.SAML2LogoutResponseBuilder;
import org.keycloak.protocol.saml.SAMLRequestParser;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlProtocolUtils;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
import org.picketlink.common.constants.GeneralConstants;
import org.picketlink.common.constants.JBossSAMLConstants;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ConfigurationException;
import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.common.util.DocumentUtil;
import org.picketlink.common.util.StaxParserUtil;
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
import org.picketlink.identity.federation.saml.v2.assertion.EncryptedAssertionType;
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAMLEndpoint {
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT";
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
protected RealmModel realm;
protected EventBuilder event;
protected SAMLIdentityProviderConfig config;
protected IdentityProvider.AuthenticationCallback callback;
@Context
private UriInfo uriInfo;
@Context
private KeycloakSession session;
@Context
private ClientConnection clientConnection;
@Context
private HttpHeaders headers;
public SAMLEndpoint(RealmModel realm, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback) {
this.realm = realm;
this.config = config;
this.callback = callback;
}
@GET
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
}
/**
*/
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
return new PostBinding().execute(samlRequest, samlResponse, relayState);
}
protected abstract class Binding {
private boolean checkSsl() {
if (uriInfo.getBaseUri().getScheme().equals("https")) {
return true;
} else {
return !realm.getSslRequired().isRequired(clientConnection);
}
}
protected Response basicChecks(String samlRequest, String samlResponse) {
if (!checkSsl()) {
event.event(EventType.LOGIN);
event.error(Errors.SSL_REQUIRED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, 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);
}
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 null;
}
protected abstract String getBindingType();
protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
protected PublicKey getIDPKey() {
X509Certificate certificate = null;
try {
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
} catch (ProcessingException e) {
throw new RuntimeException(e);
}
return certificate.getPublicKey();
}
public Response execute(String samlRequest, String samlResponse, String relayState) {
event = new EventBuilder(realm, session, clientConnection);
Response response = basicChecks(samlRequest, samlResponse);
if (response != null) return response;
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
else return handleSamlResponse(samlResponse, relayState);
}
protected Response handleSamlRequest(String samlRequest, String relayState) {
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
// validate destination
if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
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);
}
if (config.isValidateSignature()) {
try {
verifySignature(holder);
} catch (VerificationException e) {
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);
}
}
if (requestAbstractType instanceof LogoutRequestType) {
logger.debug("** logout request");
event.event(EventType.LOGOUT);
LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
return logoutRequest(logout, relayState);
} else {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
}
}
protected Response logoutRequest(LogoutRequestType request, String relayState) {
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
for (UserSessionModel userSession : userSessions) {
try {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
} catch (Exception e) {
logger.warn("failed to do backchannel logout for userSession", e);
}
}
} else {
for (String sessionIndex : request.getSessionIndex()) {
String brokerSessionId = brokerUserId + "." + sessionIndex;
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
if (userSession != null) {
try {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
} catch (Exception e) {
logger.warn("failed to do backchannel logout for userSession", e);
}
}
}
}
String issuerURL = getEntityId(uriInfo, realm);
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(request.getID());
builder.destination(config.getSingleLogoutServiceUrl());
builder.issuer(issuerURL);
builder.relayState(relayState);
if (config.isWantAuthnRequestsSigned()) {
builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signDocument();
}
try {
if (config.isPostBindingResponse()) {
return builder.postBinding().response();
} else {
return builder.redirectBinding().response();
}
} catch (ConfigurationException e) {
throw new RuntimeException(e);
} catch (ProcessingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
}
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
try {
AssertionType assertion = getAssertion(responseType);
SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
Map<String, String> notes = new HashMap<>();
notes.put(SAML_FEDERATED_SUBJECT, subjectNameID.getValue());
if (subjectNameID.getFormat() != null) notes.put(SAML_FEDERATED_SUBJECT_NAMEFORMAT, subjectNameID.getFormat().toString());
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
identity.setUsername(subjectNameID.getValue());
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
identity.setEmail(subjectNameID.getValue());
}
if (config.isStoreToken()) {
identity.setToken(samlResponse);
}
AuthnStatementType authn = null;
for (Object statement : assertion.getStatements()) {
if (statement instanceof AuthnStatementType) {
authn = (AuthnStatementType)statement;
break;
}
}
String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
identity.setBrokerUserId(brokerUserId);
if (authn != null && authn.getSessionIndex() != null) {
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
notes.put(SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
}
return callback.authenticated(notes, config, identity, relayState);
} catch (Exception e) {
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
}
}
private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
if (assertions.isEmpty()) {
throw new IdentityBrokerException("No assertion from response.");
}
ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
if (encryptedAssertion != null) {
decryptAssertion(responseType, realm.getPrivateKey());
}
return responseType.getAssertions().get(0).getAssertion();
}
public Response handleSamlResponse(String samlResponse, String relayState) {
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
// validate destination
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
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);
}
if (config.isValidateSignature()) {
try {
verifySignature(holder);
} catch (VerificationException e) {
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);
}
}
if (statusResponse instanceof ResponseType) {
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
} else {
// todo need to check that it is actually a LogoutResponse
return handleLogoutResponse(holder, statusResponse, relayState);
}
//throw new RuntimeException("Unknown response type");
}
protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
if (relayState == 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);
}
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);
}
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 AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
}
protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
SAML2Response saml2Response = new SAML2Response();
try {
Document doc = saml2Response.convert(responseType);
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
if (enc == null) {
throw new IdentityBrokerException("No encrypted assertion found.");
}
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
Document newDoc = DocumentUtil.createDocument();
Node importedNode = newDoc.importNode(enc, true);
newDoc.appendChild(importedNode);
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
SAMLParser parser = new SAMLParser();
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
.getNodeAsStream(decryptedDocumentElement)));
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
return responseType;
} catch (Exception e) {
throw new IdentityBrokerException("Could not decrypt assertion.", e);
}
}
}
protected class PostBinding extends Binding {
@Override
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
}
@Override
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
}
@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
return SAMLRequestParser.parseResponsePostBinding(response);
}
@Override
protected String getBindingType() {
return SamlProtocol.SAML_POST_BINDING;
}
}
protected class RedirectBinding extends Binding {
@Override
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
PublicKey publicKey = getIDPKey();
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
}
@Override
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
}
@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
return SAMLRequestParser.parseRequestRedirectBinding(response);
}
@Override
protected String getBindingType() {
return SamlProtocol.SAML_REDIRECT_BINDING;
}
}
}
@@ -1,185 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.saml;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* @author Pedro Igor
*/
public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityProviderConfig> {
public SAMLIdentityProvider(SAMLIdentityProviderConfig config) {
super(config);
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
return new SAMLEndpoint(realm, getConfig(), callback);
}
@Override
public Response handleRequest(AuthenticationRequest request) {
try {
UriInfo uriInfo = request.getUriInfo();
RealmModel realm = request.getRealm();
String issuerURL = getEntityId(uriInfo, realm);
String destinationUrl = getConfig().getSingleSignOnServiceUrl();
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
if (nameIDPolicyFormat == null) {
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
}
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
String assertionConsumerServiceUrl = request.getRedirectUri();
if (getConfig().isPostBindingResponse()) {
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
}
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
.assertionConsumerUrl(assertionConsumerServiceUrl)
.destination(destinationUrl)
.issuer(issuerURL)
.forceAuthn(getConfig().isForceAuthn())
.protocolBinding(protocolBinding)
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat))
.relayState(request.getState());
if (getConfig().isWantAuthnRequestsSigned()) {
PrivateKey privateKey = realm.getPrivateKey();
PublicKey publicKey = realm.getPublicKey();
if (privateKey == null) {
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a private key.");
}
if (publicKey == null) {
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a public key.");
}
KeyPair keypair = new KeyPair(publicKey, privateKey);
authnRequestBuilder.signWith(keypair);
authnRequestBuilder.signDocument();
}
if (getConfig().isPostBindingAuthnRequest()) {
return authnRequestBuilder.postBinding().request();
} else {
return authnRequestBuilder.redirectBinding().request();
}
} catch (Exception e) {
throw new IdentityBrokerException("Could not create authentication request.", e);
}
}
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
}
@Override
public Response retrieveToken(FederatedIdentityModel identity) {
return Response.ok(identity.getToken()).build();
}
@Override
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
if (getConfig().getSingleLogoutServiceUrl() == null || getConfig().getSingleLogoutServiceUrl().trim().equals("")) return null;
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
.issuer(getEntityId(uriInfo, realm))
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
.destination(getConfig().getSingleLogoutServiceUrl());
if (getConfig().isWantAuthnRequestsSigned()) {
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signDocument();
}
try {
return logoutBuilder.relayState(userSession.getId()).postBinding().request();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
String authnBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
if (getConfig().isPostBindingAuthnRequest()) {
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
}
String endpoint = uriInfo.getBaseUriBuilder()
.path("realms").path(realm.getName())
.path("broker")
.path(getConfig().getAlias())
.path("endpoint")
.build().toString();
String descriptor =
"<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
" </NameIDFormat>\n" +
" <SingleLogoutService Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"/>\n" +
" <AssertionConsumerService\n" +
" Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"\n" +
" index=\"1\" isDefault=\"true\" />\n";
if (getConfig().isWantAuthnRequestsSigned()) {
descriptor +=
" <KeyDescriptor use=\"signing\">\n" +
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
" <dsig:X509Data>\n" +
" <dsig:X509Certificate>\n" + realm.getCertificatePem() + "\n" +
" </dsig:X509Certificate>\n" +
" </dsig:X509Data>\n" +
" </dsig:KeyInfo>\n" +
" </KeyDescriptor>\n";
}
descriptor +=
" </SPSSODescriptor>\n" +
"</EntityDescriptor>\n";
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
}
}
@@ -1,113 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.saml;
import org.keycloak.models.IdentityProviderModel;
/**
* @author Pedro Igor
*/
public class SAMLIdentityProviderConfig extends IdentityProviderModel {
public SAMLIdentityProviderConfig() {
}
public SAMLIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
super(identityProviderModel);
}
public String getSingleSignOnServiceUrl() {
return getConfig().get("singleSignOnServiceUrl");
}
public void setSingleSignOnServiceUrl(String singleSignOnServiceUrl) {
getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
}
public String getSingleLogoutServiceUrl() {
return getConfig().get("singleLogoutServiceUrl");
}
public void setSingleLogoutServiceUrl(String singleLogoutServiceUrl) {
getConfig().put("singleLogoutServiceUrl", singleLogoutServiceUrl);
}
public boolean isValidateSignature() {
return Boolean.valueOf(getConfig().get("validateSignature"));
}
public void setValidateSignature(boolean validateSignature) {
getConfig().put("validateSignature", String.valueOf(validateSignature));
}
public boolean isForceAuthn() {
return Boolean.valueOf(getConfig().get("forceAuthn"));
}
public void setForceAuthn(boolean forceAuthn) {
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
}
public String getSigningCertificate() {
return getConfig().get("signingCertificate");
}
public void setSigningCertificate(String signingCertificate) {
getConfig().put("signingCertificate", signingCertificate);
}
public String getNameIDPolicyFormat() {
return getConfig().get("nameIDPolicyFormat");
}
public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
getConfig().put("nameIDPolicyFormat", nameIDPolicyFormat);
}
public boolean isWantAuthnRequestsSigned() {
return Boolean.valueOf(getConfig().get("wantAuthnRequestsSigned"));
}
public void setWantAuthnRequestsSigned(boolean wantAuthnRequestsSigned) {
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
}
public String getEncryptionPublicKey() {
return getConfig().get("encryptionPublicKey");
}
public void setEncryptionPublicKey(String encryptionPublicKey) {
getConfig().put("encryptionPublicKey", encryptionPublicKey);
}
public boolean isPostBindingAuthnRequest() {
return Boolean.valueOf(getConfig().get("postBindingAuthnRequest"));
}
public void setPostBindingAuthnRequest(boolean postBindingAuthnRequest) {
getConfig().put("postBindingAuthnRequest", String.valueOf(postBindingAuthnRequest));
}
public boolean isPostBindingResponse() {
return Boolean.valueOf(getConfig().get("postBindingResponse"));
}
public void setPostBindingResponse(boolean postBindingResponse) {
getConfig().put("postBindingResponse", String.valueOf(postBindingResponse));
}
}
@@ -1,153 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.broker.saml;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.util.DocumentUtil;
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
import org.picketlink.identity.federation.saml.v2.metadata.EndpointType;
import org.picketlink.identity.federation.saml.v2.metadata.EntitiesDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.IDPSSODescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.KeyDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.KeyTypes;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory<SAMLIdentityProvider> {
public static final String PROVIDER_ID = "saml";
@Override
public String getName() {
return "SAML v2.0";
}
@Override
public SAMLIdentityProvider create(IdentityProviderModel model) {
return new SAMLIdentityProvider(new SAMLIdentityProviderConfig(model));
}
@Override
public Map<String, String> parseConfig(InputStream inputStream) {
try {
Object parsedObject = new SAMLParser().parse(inputStream);
EntityDescriptorType entityType;
if (EntitiesDescriptorType.class.isInstance(parsedObject)) {
entityType = (EntityDescriptorType) ((EntitiesDescriptorType) parsedObject).getEntityDescriptor().get(0);
} else {
entityType = (EntityDescriptorType) parsedObject;
}
List<EntityDescriptorType.EDTChoiceType> choiceType = entityType.getChoiceType();
if (!choiceType.isEmpty()) {
EntityDescriptorType.EDTChoiceType edtChoiceType = choiceType.get(0);
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = edtChoiceType.getDescriptors();
if (!descriptors.isEmpty()) {
EntityDescriptorType.EDTDescriptorChoiceType edtDescriptorChoiceType = descriptors.get(0);
IDPSSODescriptorType idpDescriptor = edtDescriptorChoiceType.getIdpDescriptor();
if (idpDescriptor != null) {
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();
String singleSignOnServiceUrl = null;
boolean postBinding = false;
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
singleSignOnServiceUrl = endpoint.getLocation().toString();
postBinding = true;
break;
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
singleSignOnServiceUrl = endpoint.getLocation().toString();
}
}
String singleLogoutServiceUrl = null;
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
singleLogoutServiceUrl = endpoint.getLocation().toString();
break;
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
singleLogoutServiceUrl = endpoint.getLocation().toString();
break;
}
}
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
String defaultCertificate = null;
if (keyDescriptor != null) {
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
Element keyInfo = keyDescriptorType.getKeyInfo();
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
} else if (keyDescriptorType.getUse() == null) {
defaultCertificate = x509KeyInfo.getTextContent();
}
}
}
if (defaultCertificate != null) {
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
}
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
}
}
return samlIdentityProviderConfig.getConfig();
}
}
}
} catch (ParsingException pe) {
throw new RuntimeException("Could not parse IdP SAML Metadata", pe);
}
return new HashMap<String, String>();
}
@Override
public String getId() {
return PROVIDER_ID;
}
}
@@ -1 +0,0 @@
org.keycloak.broker.saml.SAMLIdentityProviderFactory
-47
View File
@@ -1,47 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-connections-file</artifactId>
<name>Keycloak Connections File</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-single-file</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,86 +0,0 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.connections.file;
import org.keycloak.models.KeycloakSession;
/**
* Provides the InMemoryModel and notifies the factory to save it when
* the session is done.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class DefaultFileConnectionProvider implements FileConnectionProvider {
private final DefaultFileConnectionProviderFactory factory;
private final KeycloakSession session;
private final InMemoryModel inMemoryModel;
private boolean isRollbackOnly = false;
public DefaultFileConnectionProvider(DefaultFileConnectionProviderFactory factory,
KeycloakSession session,
InMemoryModel inMemoryModel) {
this.factory = factory;
this.session = session;
this.inMemoryModel = inMemoryModel;
}
@Override
public InMemoryModel getModel() {
return inMemoryModel;
}
@Override
public void sessionClosed(KeycloakSession session) {
factory.sessionClosed(session);
}
@Override
public void close() {
}
@Override
public void begin() {
}
@Override
public void commit() {
factory.commit(session);
}
@Override
public void rollback() {
factory.rollback(session);
}
@Override
public void setRollbackOnly() {
isRollbackOnly = true;
}
@Override
public boolean getRollbackOnly() {
return isRollbackOnly;
}
@Override
public boolean isActive() {
return factory.isActive(session);
}
}
@@ -1,202 +0,0 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.connections.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.exportimport.Strategy;
import org.keycloak.exportimport.util.ExportUtils;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
/**
* This class dispenses a FileConnectionProvider to Keycloak sessions. It
* makes sure that only one InMemoryModel is provided for each session and it
* handles thread contention for the file where the model is read or saved.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class DefaultFileConnectionProviderFactory implements FileConnectionProviderFactory {
protected static final Logger logger = Logger.getLogger(DefaultFileConnectionProviderFactory.class);
private File kcdata;
private final Map<KeycloakSession, FileConnectionProvider> allProviders = new HashMap<KeycloakSession, FileConnectionProvider>();
@Override
public void init(Config.Scope config) {
String fileName = config.get("fileName");
if (fileName == null) {
fileName = "keycloak-model.json";
}
String directory = config.get("directory");
if (directory == null) {
directory = System.getProperty("jboss.server.data.dir");
}
if (directory == null) {
directory = ".";
}
kcdata = new File(directory, fileName);
}
public void sessionClosed(KeycloakSession session) {
synchronized(allProviders) {
allProviders.remove(session);
//logger.info("Removed session " + session.hashCode());
//logger.info("sessionClosed: Session count=" + allModels.size());
}
}
void readModelFile(KeycloakSession session) {
synchronized(allProviders) {
if (!kcdata.exists()) {
return;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(kcdata);
ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING);
} catch (IOException ioe) {
logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe);
} finally {
//logger.info("Read model file for session=" + session.hashCode());
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
logger.error("Failed to close output stream.", e);
}
}
}
}
void writeModelFile(KeycloakSession session) {
synchronized(allProviders) {
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(kcdata);
exportModel(session, outStream);
} catch (IOException e) {
logger.error("Unable to write model file " + kcdata.getAbsolutePath(), e);
} finally {
//logger.info("Wrote model file for session=" + session.hashCode());
try {
if (outStream != null) {
outStream.close();
}
} catch (IOException e) {
logger.error("Failed to close output stream.", e);
}
}
}
}
private void exportModel(KeycloakSession session, FileOutputStream outStream) throws IOException {
List<RealmModel> realms = session.realms().getRealms();
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
for (RealmModel realm : realms) {
reps.add(ExportUtils.exportRealm(session, realm, true));
}
JsonSerialization.prettyMapper.writeValue(outStream, reps);
}
@Override
public FileConnectionProvider create(KeycloakSession session) {
synchronized (allProviders) {
FileConnectionProvider fcProvider = allProviders.get(session);
if (fcProvider == null) {
InMemoryModel model = new InMemoryModel();
fcProvider = new DefaultFileConnectionProvider(this, session, model);
allProviders.put(session, fcProvider);
session.getTransaction().enlist(fcProvider);
readModelFile(session);
//logger.info("Added session " + session.hashCode() + " total sessions=" + allModels.size());
}
return fcProvider;
}
}
// commitCount is used for debugging. This allows you to easily run a test
// to a particular point and then examine the JSON file.
//private static int commitCount = 0;
void commit(KeycloakSession session) {
//commitCount++;
synchronized (allProviders) {
// in case commit was somehow called twice on the same session
if (!allProviders.containsKey(session)) return;
try {
writeModelFile(session);
} finally {
allProviders.remove(session);
//logger.info("Removed session " + session.hashCode());
//logger.info("*** commitCount=" + commitCount);
//logger.info("commit(): Session count=" + allModels.size());
}
// if (commitCount == 16) {Thread.dumpStack();System.exit(0);}
}
}
void rollback(KeycloakSession session) {
synchronized (allProviders) {
allProviders.remove(session);
//logger.info("rollback(): Session count=" + allModels.size());
}
}
boolean isActive(KeycloakSession session) {
synchronized (allProviders) {
return allProviders.containsKey(session);
}
}
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.connections.file;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.provider.Provider;
/**
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public interface FileConnectionProvider extends Provider, KeycloakTransaction {
InMemoryModel getModel();
void sessionClosed(KeycloakSession session);
}
@@ -1,25 +0,0 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.connections.file;
import org.keycloak.provider.ProviderFactory;
/**
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public interface FileConnectionProviderFactory extends ProviderFactory<FileConnectionProvider> {
}
@@ -1,27 +0,0 @@
package org.keycloak.connections.file;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class FileConnectionSpi implements Spi {
@Override
public String getName() {
return "connectionsFile";
}
@Override
public Class<? extends Provider> getProviderClass() {
return FileConnectionProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return FileConnectionProviderFactory.class;
}
}
@@ -1,99 +0,0 @@
/*
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.keycloak.connections.file;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
* This class provides an in-memory copy of the entire model for each
* Keycloak session. At the start of the session, the model is read
* from JSON. When the session's transaction ends, the model is written back
* out.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
*/
public class InMemoryModel {
private final Map<String, RealmModel> allRealms = new HashMap<String, RealmModel>();
// realmId, userId, userModel
private final Map<String, Map<String,UserModel>> allUsers = new HashMap<String, Map<String,UserModel>>();
public InMemoryModel() {
}
public void putRealm(String id, RealmModel realm) {
allRealms.put(id, realm);
allUsers.put(id, new HashMap<String, UserModel>());
}
public RealmModel getRealm(String id) {
return allRealms.get(id);
}
public Collection<RealmModel> getRealms() {
return allRealms.values();
}
public RealmModel getRealmByName(String name) {
for (RealmModel realm : getRealms()) {
if (realm.getName().equals(name)) return realm;
}
return null;
}
public boolean removeRealm(String id) {
allUsers.remove(id);
return (allRealms.remove(id) != null);
}
protected Map<String, UserModel> realmUsers(String realmId) {
Map<String, UserModel> realmUsers = allUsers.get(realmId);
if (realmUsers == null) throw new NullPointerException("Realm users not found for id=" + realmId);
return realmUsers;
}
public void putUser(String realmId, String userId, UserModel user) {
realmUsers(realmId).put(userId, user);
}
public UserModel getUser(String realmId, String userId) {
return realmUsers(realmId).get(userId);
}
public boolean hasUserWithUsername(String realmId, String username) {
for (UserModel user : getUsers(realmId)) {
if (user.getUsername().equals(username)) return true;
}
return false;
}
public Collection<UserModel> getUsers(String realmId) {
return realmUsers(realmId).values();
}
public boolean removeUser(String realmId, String userId) {
return (realmUsers(realmId).remove(userId) != null);
}
}
@@ -1 +0,0 @@
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
@@ -1 +0,0 @@
org.keycloak.connections.file.FileConnectionSpi
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -9,7 +9,6 @@ import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.naming.InitialContext;
@@ -51,11 +50,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
private void lazyInit() {
if (cacheManager == null) {
synchronized (this) {
@@ -87,13 +81,10 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
boolean clustered = config.getBoolean("clustered", false);
boolean async = config.getBoolean("async", true);
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
cacheManager = new DefaultCacheManager(gcb.build());
containerManaged = false;
+1 -7
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -19,11 +19,6 @@
<artifactId>keycloak-connections-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
@@ -38,7 +33,6 @@
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
</dependencies>
<build>
@@ -28,7 +28,6 @@ import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.servicelocator.ServiceLocator;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import java.sql.Connection;
import java.sql.ResultSet;
@@ -51,12 +50,9 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
@Override
public void update(KeycloakSession session, Connection connection) {
public void update(Connection connection) {
logger.debug("Starting database update");
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
ThreadLocalSessionContext.setCurrentSession(session);
try {
Liquibase liquibase = getLiquibase(connection);
@@ -68,7 +64,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
statement.executeQuery("SELECT id FROM REALM");
logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId());
liquibase.markNextChangeSetRan(null);
liquibase.markNextChangeSetRan((Contexts) null);
} catch (SQLException e) {
logger.info("Initializing database schema");
}
@@ -85,10 +81,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
} catch (Exception e) {
throw new RuntimeException("Failed to update database", e);
} finally {
ThreadLocalSessionContext.removeCurrentSession();
}
logger.debug("Completed database update");
}
@@ -4,7 +4,6 @@ import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -20,11 +19,6 @@ public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFac
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@@ -1,23 +0,0 @@
package org.keycloak.connections.jpa.updater.liquibase;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ThreadLocalSessionContext {
private static final ThreadLocal<KeycloakSession> currentSession = new ThreadLocal<KeycloakSession>();
public static KeycloakSession getCurrentSession() {
return currentSession.get();
}
public static void setCurrentSession(KeycloakSession session) {
currentSession.set(session);
}
public static void removeCurrentSession() {
currentSession.remove();
}
}
@@ -1,97 +0,0 @@
package org.keycloak.connections.jpa.updater.liquibase.custom;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import liquibase.change.custom.CustomSqlChange;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
import liquibase.snapshot.SnapshotGeneratorFactory;
import liquibase.statement.SqlStatement;
import liquibase.structure.core.Table;
import org.keycloak.connections.jpa.updater.liquibase.ThreadLocalSessionContext;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class CustomKeycloakTask implements CustomSqlChange {
protected KeycloakSession kcSession;
protected Database database;
protected JdbcConnection jdbcConnection;
protected Connection connection;
protected StringBuilder confirmationMessage = new StringBuilder();
protected List<SqlStatement> statements = new ArrayList<SqlStatement>();
@Override
public ValidationErrors validate(Database database) {
return null;
}
@Override
public void setFileOpener(ResourceAccessor resourceAccessor) {
}
@Override
public String getConfirmationMessage() {
return confirmationMessage.toString();
}
@Override
public void setUp() throws SetupException {
this.kcSession = ThreadLocalSessionContext.getCurrentSession();
if (this.kcSession == null) {
throw new SetupException("No KeycloakSession provided in ThreadLocal");
}
}
@Override
public SqlStatement[] generateStatements(Database database) throws CustomChangeException {
this.database = database;
jdbcConnection = (JdbcConnection) database.getConnection();
connection = jdbcConnection.getWrappedConnection();
if (isApplicable()) {
confirmationMessage.append(getTaskId() + ": ");
generateStatementsImpl();
} else {
confirmationMessage.append(getTaskId() + ": no update applicable for this task");
}
return statements.toArray(new SqlStatement[statements.size()]);
}
protected boolean isApplicable() throws CustomChangeException {
try {
String correctedTableName = database.correctObjectName("REALM", Table.class);
if (SnapshotGeneratorFactory.getInstance().has(new Table().setName(correctedTableName), database)) {
ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM REALM");
try {
return (resultSet.next());
} finally {
resultSet.close();
}
} else {
return false;
}
} catch (Exception e) {
throw new CustomChangeException("Failed to check database availability", e);
}
}
/**
* It's supposed to fill SQL statements to the "statements" variable and fill "confirmationMessage"
*/
protected abstract void generateStatementsImpl() throws CustomChangeException;
protected abstract String getTaskId();
}
@@ -1,338 +0,0 @@
package org.keycloak.connections.jpa.updater.liquibase.custom;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import liquibase.exception.CustomChangeException;
import liquibase.exception.DatabaseException;
import liquibase.statement.core.InsertStatement;
import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Table;
import org.keycloak.Config;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
private String realmTableName;
@Override
protected String getTaskId() {
return "Update 1.2.0.Beta1";
}
@Override
protected void generateStatementsImpl() throws CustomChangeException {
realmTableName = database.correctObjectName("REALM", Table.class);
try {
convertSocialToIdFedRealms();
convertSocialToIdFedUsers();
addAccessCodeLoginTimeout();
addNewAdminRoles();
addDefaultProtocolMappers();
} catch (Exception e) {
throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e);
}
}
protected void convertSocialToIdFedRealms() throws SQLException, DatabaseException {
String identityProviderTableName = database.correctObjectName("IDENTITY_PROVIDER", Table.class);
String idpConfigTableName = database.correctObjectName("IDENTITY_PROVIDER_CONFIG", Table.class);
PreparedStatement statement = jdbcConnection.prepareStatement("select RSC.NAME, VALUE, REALM_ID, UPDATE_PROFILE_ON_SOC_LOGIN from REALM_SOCIAL_CONFIG RSC,REALM where RSC.REALM_ID = REALM.ID ORDER BY RSC.REALM_ID, RSC.NAME");
try {
ResultSet resultSet = statement.executeQuery();
try {
boolean providerInProgress = false;
String socialProviderId = null;
String clientId = null;
String clientSecret;
String realmId = null;
boolean updateProfileOnSocialLogin = false;
boolean first = true;
while (resultSet.next()) {
if (first) {
confirmationMessage.append("Migrating social to identity providers: ");
first = false;
}
if (!providerInProgress) {
String key = resultSet.getString("NAME");
int keyIndex = key.indexOf(".key");
if (keyIndex == -1) {
throw new IllegalStateException("Can't parse the provider from column: " + key);
}
socialProviderId = key.substring(0, keyIndex);
clientId = resultSet.getString("VALUE");
realmId = resultSet.getString("REALM_ID");
updateProfileOnSocialLogin = resultSet.getBoolean("UPDATE_PROFILE_ON_SOC_LOGIN");
providerInProgress = true;
} else {
clientSecret = resultSet.getString("VALUE");
String internalId = KeycloakModelUtils.generateId();
InsertStatement idpInsert = new InsertStatement(null, null, identityProviderTableName)
.addColumnValue("INTERNAL_ID", internalId)
.addColumnValue("ENABLED", true)
.addColumnValue("PROVIDER_ALIAS", socialProviderId)
.addColumnValue("PROVIDER_ID", socialProviderId)
.addColumnValue("UPDATE_PROFILE_FIRST_LOGIN", updateProfileOnSocialLogin)
.addColumnValue("STORE_TOKEN", false)
.addColumnValue("AUTHENTICATE_BY_DEFAULT", false)
.addColumnValue("REALM_ID", realmId);
InsertStatement clientIdInsert = new InsertStatement(null, null, idpConfigTableName)
.addColumnValue("IDENTITY_PROVIDER_ID", internalId)
.addColumnValue("NAME", "clientId")
.addColumnValue("VALUE", clientId);
InsertStatement clientSecretInsert = new InsertStatement(null, null, idpConfigTableName)
.addColumnValue("IDENTITY_PROVIDER_ID", internalId)
.addColumnValue("NAME", "clientSecret")
.addColumnValue("VALUE", clientSecret);
statements.add(idpInsert);
statements.add(clientIdInsert);
statements.add(clientSecretInsert);
confirmationMessage.append(socialProviderId + " in realm " + realmId + ", ");
providerInProgress = false;
}
}
// It means that some provider where processed
if (!first) {
confirmationMessage.append(". ");
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
protected void convertSocialToIdFedUsers() throws SQLException, DatabaseException {
String federatedIdentityTableName = database.correctObjectName("FEDERATED_IDENTITY", Table.class);
PreparedStatement statement = jdbcConnection.prepareStatement("select REALM_ID, USER_ID, SOCIAL_PROVIDER, SOCIAL_USER_ID, SOCIAL_USERNAME from USER_SOCIAL_LINK");
try {
ResultSet resultSet = statement.executeQuery();
try {
int count = 0;
while (resultSet.next()) {
InsertStatement insert = new InsertStatement(null, null, federatedIdentityTableName)
.addColumnValue("REALM_ID", resultSet.getString("REALM_ID"))
.addColumnValue("USER_ID", resultSet.getString("USER_ID"))
.addColumnValue("IDENTITY_PROVIDER", resultSet.getString("SOCIAL_PROVIDER"))
.addColumnValue("FEDERATED_USER_ID", resultSet.getString("SOCIAL_USER_ID"))
.addColumnValue("FEDERATED_USERNAME", resultSet.getString("SOCIAL_USERNAME"));
count++;
statements.add(insert);
}
confirmationMessage.append("Updating " + count + " social links to federated identities. ");
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
protected void addAccessCodeLoginTimeout() {
UpdateStatement statement = new UpdateStatement(null, null, realmTableName)
.addNewColumnValue("LOGIN_LIFESPAN", 1800)
.setWhereClause("LOGIN_LIFESPAN IS NULL");
statements.add(statement);
confirmationMessage.append("Updated LOGIN_LIFESPAN of all realms to 1800 seconds. ");
}
private void addNewAdminRoles() throws SQLException, DatabaseException{
addNewMasterAdminRoles();
addNewRealmAdminRoles();
confirmationMessage.append("Adding new admin roles. ");
}
protected void addNewMasterAdminRoles() throws SQLException, DatabaseException {
// Retrieve ID of admin role of master realm
String adminRoleId = getAdminRoleId();
String masterRealmId = Config.getAdminRealm();
PreparedStatement statement = jdbcConnection.prepareStatement("select NAME from REALM");
try {
ResultSet resultSet = statement.executeQuery();
try {
while (resultSet.next()) {
String realmName = resultSet.getString("NAME");
String masterAdminAppName = realmName + "-realm";
PreparedStatement statement2 = jdbcConnection.prepareStatement("select ID from CLIENT where REALM_ID = ? AND NAME = ?");
statement2.setString(1, masterRealmId);
statement2.setString(2, masterAdminAppName);
try {
ResultSet resultSet2 = statement2.executeQuery();
try {
if (resultSet2.next()) {
String masterAdminAppId = resultSet2.getString("ID");
addAdminRole(AdminRoles.VIEW_IDENTITY_PROVIDERS, masterRealmId, masterAdminAppId, adminRoleId);
addAdminRole(AdminRoles.MANAGE_IDENTITY_PROVIDERS, masterRealmId, masterAdminAppId, adminRoleId);
} else {
throw new IllegalStateException("Couldn't find ID of '" + masterAdminAppName + "' application in 'master' realm. ");
}
} finally {
resultSet2.close();
}
} finally {
statement2.close();
}
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
private String getAdminRoleId() throws SQLException, DatabaseException {
PreparedStatement statement = jdbcConnection.prepareStatement("select ID from KEYCLOAK_ROLE where NAME = ? AND REALM = ?");
statement.setString(1, AdminRoles.ADMIN);
statement.setString(2, Config.getAdminRealm());
try {
ResultSet resultSet = statement.executeQuery();
try {
if (resultSet.next()) {
return resultSet.getString("ID");
} else {
throw new IllegalStateException("Couldn't find ID of 'admin' role in 'master' realm");
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
protected void addNewRealmAdminRoles() throws SQLException, DatabaseException {
PreparedStatement statement = jdbcConnection.prepareStatement("select CLIENT.ID REALM_ADMIN_APP_ID, CLIENT.REALM_ID REALM_ID, KEYCLOAK_ROLE.ID ADMIN_ROLE_ID from CLIENT,KEYCLOAK_ROLE where KEYCLOAK_ROLE.APPLICATION = CLIENT.ID AND CLIENT.NAME = 'realm-management' AND KEYCLOAK_ROLE.NAME = ?");
statement.setString(1, AdminRoles.REALM_ADMIN);
try {
ResultSet resultSet = statement.executeQuery();
try {
while (resultSet.next()) {
String realmAdminAppId = resultSet.getString("REALM_ADMIN_APP_ID");
String realmId = resultSet.getString("REALM_ID");
String adminRoleId = resultSet.getString("ADMIN_ROLE_ID");
addAdminRole(AdminRoles.VIEW_IDENTITY_PROVIDERS, realmId, realmAdminAppId, adminRoleId);
addAdminRole(AdminRoles.MANAGE_IDENTITY_PROVIDERS, realmId, realmAdminAppId, adminRoleId);
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
private void addAdminRole(String roleName, String realmId, String applicationId, String realmAdminAppRoleId) {
String roleTableName = database.correctObjectName("KEYCLOAK_ROLE", Table.class);
String compositeRoleTableName = database.correctObjectName("COMPOSITE_ROLE", Table.class);
String newRoleId = KeycloakModelUtils.generateId();
InsertStatement insertRole = new InsertStatement(null, null, roleTableName)
.addColumnValue("ID", newRoleId)
.addColumnValue("APP_REALM_CONSTRAINT", applicationId)
.addColumnValue("APPLICATION_ROLE", true)
.addColumnValue("NAME", roleName)
.addColumnValue("REALM_ID", realmId)
.addColumnValue("APPLICATION", applicationId);
// Add newly created role to the composite roles of 'realm-admin' role
InsertStatement insertCompRole = new InsertStatement(null, null, compositeRoleTableName)
.addColumnValue("COMPOSITE", realmAdminAppRoleId)
.addColumnValue("CHILD_ROLE", newRoleId);
statements.add(insertRole);
statements.add(insertCompRole);
}
protected void addDefaultProtocolMappers() throws SQLException, DatabaseException {
String protocolMapperTableName = database.correctObjectName("PROTOCOL_MAPPER", Table.class);
String protocolMapperCfgTableName = database.correctObjectName("PROTOCOL_MAPPER_CONFIG", Table.class);
PreparedStatement statement = jdbcConnection.prepareStatement("select ID, NAME, ALLOWED_CLAIMS_MASK from CLIENT");
try {
ResultSet resultSet = statement.executeQuery();
try {
boolean first = true;
while (resultSet.next()) {
if (first) {
confirmationMessage.append("Migrating claimsMask to protocol mappers for clients: ");
first = false;
}
Object acmObj = resultSet.getObject("ALLOWED_CLAIMS_MASK");
long mask = (acmObj != null) ? (Long) acmObj : ClaimMask.ALL;
MigrationProvider migrationProvider = this.kcSession.getProvider(MigrationProvider.class);
List<ProtocolMapperRepresentation> protocolMappers = migrationProvider.getMappersForClaimMask(mask);
for (ProtocolMapperRepresentation protocolMapper : protocolMappers) {
String mapperId = KeycloakModelUtils.generateId();
InsertStatement insert = new InsertStatement(null, null, protocolMapperTableName)
.addColumnValue("ID", mapperId)
.addColumnValue("PROTOCOL", protocolMapper.getProtocol())
.addColumnValue("NAME", protocolMapper.getName())
.addColumnValue("CONSENT_REQUIRED", protocolMapper.isConsentRequired())
.addColumnValue("CONSENT_TEXT", protocolMapper.getConsentText())
.addColumnValue("PROTOCOL_MAPPER_NAME", protocolMapper.getProtocolMapper())
.addColumnValue("CLIENT_ID", resultSet.getString("ID"));
statements.add(insert);
for (Map.Entry<String, String> cfgEntry : protocolMapper.getConfig().entrySet()) {
InsertStatement cfgInsert = new InsertStatement(null, null, protocolMapperCfgTableName)
.addColumnValue("PROTOCOL_MAPPER_ID", mapperId)
.addColumnValue("NAME", cfgEntry.getKey())
.addColumnValue("VALUE", cfgEntry.getValue());
statements.add(cfgInsert);
}
}
confirmationMessage.append(resultSet.getString("NAME") + ", ");
}
// It means that some provider where processed
if (!first) {
confirmationMessage.append(". ");
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
}
@@ -1,172 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="psilva@redhat.com" id="1.2.0.Beta1">
<delete tableName="CLIENT_SESSION_ROLE"/>
<delete tableName="CLIENT_SESSION_NOTE"/>
<delete tableName="CLIENT_SESSION"/>
<delete tableName="USER_SESSION"/>
<createTable tableName="PROTOCOL_MAPPER">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="PROTOCOL" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="PROTOCOL_MAPPER_NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="CONSENT_TEXT" type="VARCHAR(255)"/>
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="PROTOCOL_MAPPER_CONFIG">
<column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="CLOB"/>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="FEDERATED_IDENTITY">
<column name="IDENTITY_PROVIDER" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="REALM_ID" type="VARCHAR(36)"/>
<column name="FEDERATED_USER_ID" type="VARCHAR(255)"/>
<column name="FEDERATED_USERNAME" type="VARCHAR(255)"/>
<column name="TOKEN" type="TEXT"/>
<column name="USER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="IDENTITY_PROVIDER">
<column name="INTERNAL_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="PROVIDER_ALIAS" type="VARCHAR(255)"/>
<column name="PROVIDER_ID" type="VARCHAR(255)"/>
<column name="UPDATE_PROFILE_FIRST_LOGIN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="STORE_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="AUTHENTICATE_BY_DEFAULT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="REALM_ID" type="VARCHAR(36)"/>
</createTable>
<createTable tableName="IDENTITY_PROVIDER_CONFIG">
<column name="IDENTITY_PROVIDER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="CLOB"/>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="CLIENT_IDENTITY_PROV_MAPPING">
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="IDENTITY_PROVIDER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="RETRIEVE_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="REALM_SUPPORTED_LOCALES">
<column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="VARCHAR(255)"/>
</createTable>
<createTable tableName="USER_SESSION_NOTE">
<column name="USER_SESSION" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="VARCHAR(2048)"/>
</createTable>
<addColumn tableName="CLIENT">
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="USER_SESSION">
<column name="USER_SESSION_STATE" type="INT" />
<column name="BROKER_SESSION_ID" type="VARCHAR(255)" />
<column name="BROKER_USER_ID" type="VARCHAR(255)" />
</addColumn>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_PCM" tableName="PROTOCOL_MAPPER"/>
<addPrimaryKey columnNames="INTERNAL_ID" constraintName="CONSTRAINT_2B" tableName="IDENTITY_PROVIDER"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER, USER_ID" constraintName="CONSTRAINT_40" tableName="FEDERATED_IDENTITY"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER_ID, NAME" constraintName="CONSTRAINT_D" tableName="IDENTITY_PROVIDER_CONFIG"/>
<addPrimaryKey columnNames="PROTOCOL_MAPPER_ID, NAME" constraintName="CONSTRAINT_PMConfig" tableName="PROTOCOL_MAPPER_CONFIG"/>
<addPrimaryKey columnNames="USER_SESSION, NAME" constraintName="CONSTRAINT_USN_PK" tableName="USER_SESSION_NOTE"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_PCM_REALM" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="CLIENT_IDENTITY_PROV_MAPPING" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROV_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SUPPORTED_LOCALES" constraintName="FK_SUPPORTED_LOCALES_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="USER_SESSION" baseTableName="USER_SESSION_NOTE" constraintName="FK5EDFB00FF51D3472" referencedColumnNames="ID" referencedTableName="USER_SESSION"/>
<addUniqueConstraint columnNames="PROVIDER_ALIAS, REALM_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
<addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROV_MAPPING"/>
<addColumn tableName="REALM">
<column name="LOGIN_LIFESPAN" type="INT"/>
<column name="INTERNATIONALIZATION_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="DEFAULT_LOCALE" type="VARCHAR(255)" />
<column name="REG_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<!-- KEYCLOAK-1106 APPLICATION_ID and REALM_ID switched in REALM_APPLICATION table -->
<dropForeignKeyConstraint baseTableName="REALM_APPLICATION" constraintName="FK_71S3P0DIUXAWWQQSA528UBY2Q" />
<dropForeignKeyConstraint baseTableName="REALM_APPLICATION" constraintName="FK_L5QGA3RFME47335JY8JXYXH3I" />
<dropUniqueConstraint tableName="REALM_APPLICATION" constraintName="UK_L5QGA3RFME47335JY8JXYXH3I" />
<renameColumn tableName="REALM_APPLICATION" oldColumnName="APPLICATION_ID" newColumnName="APPLICATION_ID_TMP" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="REALM_APPLICATION" oldColumnName="REALM_ID" newColumnName="APPLICATION_ID" columnDataType="VARCHAR(36)"/>
<renameColumn tableName="REALM_APPLICATION" oldColumnName="APPLICATION_ID_TMP" newColumnName="REALM_ID" columnDataType="VARCHAR(36)"/>
<addUniqueConstraint columnNames="APPLICATION_ID" constraintName="UK_M6QGA3RFME47335JY8JXYXH3I" tableName="REALM_APPLICATION"/>
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="REALM_APPLICATION" constraintName="FK_82S3P0DIUXAWWQQSA528UBY2Q" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_APPLICATION" constraintName="FK_M6QGA3RFME47335JY8JXYXH3I" referencedColumnNames="ID" referencedTableName="REALM"/>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate1_2_0_Beta1"/>
<!-- Remove obsolete 'social' and 'claims' stuff -->
<dropForeignKeyConstraint baseTableName="USER_SOCIAL_LINK" constraintName="FK_68CJYS5UWM55UY823Y75XG4OM" />
<dropTable tableName="USER_SOCIAL_LINK" cascadeConstraints="true" />
<dropForeignKeyConstraint baseTableName="REALM_SOCIAL_CONFIG" constraintName="FK_SV5I3C2TI7G0G922FGE683SOV" />
<dropTable tableName="REALM_SOCIAL_CONFIG" cascadeConstraints="true" />
<dropColumn tableName="CLIENT" columnName="ALLOWED_CLAIMS_MASK" />
<createTable tableName="REALM_ENABLED_EVENT_TYPES">
<column name="REALM_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="VARCHAR(255)"/>
</createTable>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_ENABLED_EVENT_TYPES" constraintName="FK_H846O4H0W8EPX5NWEDRF5Y69J" referencedColumnNames="ID" referencedTableName="REALM"/>
</changeSet>
</databaseChangeLog>
@@ -3,5 +3,4 @@
<include file="META-INF/jpa-changelog-1.0.0.Final.xml"/>
<include file="META-INF/jpa-changelog-1.1.0.Beta1.xml"/>
<include file="META-INF/jpa-changelog-1.1.0.Final.xml"/>
<include file="META-INF/jpa-changelog-1.2.0.Beta1.xml"/>
</databaseChangeLog>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -5,7 +5,6 @@ import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
@@ -57,11 +56,6 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
private void lazyInit(KeycloakSession session) {
if (emf == null) {
synchronized (this) {
@@ -140,7 +134,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
updater.update(session, connection);
updater.update(connection);
} else {
logger.debug("Database is up to date");
}
@@ -1,6 +1,5 @@
package org.keycloak.connections.jpa.updater;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import java.sql.Connection;
@@ -12,11 +11,11 @@ public interface JpaUpdaterProvider extends Provider {
public String FIRST_VERSION = "1.0.0.Final";
public String LAST_VERSION = "1.2.0.Beta1";
public String LAST_VERSION = "1.1.0.Beta1";
public String getCurrentVersionSql();
public void update(KeycloakSession session, Connection connection);
public void update(Connection connection);
public void validate(Connection connection);
@@ -11,21 +11,17 @@
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
<class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class>
<class>org.keycloak.models.jpa.entities.RoleEntity</class>
<class>org.keycloak.models.jpa.entities.FederatedIdentityEntity</class>
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
<class>org.keycloak.models.jpa.entities.UserEntity</class>
<class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
<class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
<class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
<!-- JpaUserSessionProvider -->
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
@@ -33,7 +29,7 @@
<class>org.keycloak.events.jpa.EventEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="jboss.as.jpa.managed" value="false"/>
</properties>
-42
View File
@@ -1,42 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-connections-mongo-update</artifactId>
<name>Keycloak Connections Mongo Update</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-mongo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,269 +0,0 @@
package org.keycloak.connections.mongo.updater.impl.updates;
import java.util.List;
import java.util.Map;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.keycloak.Config;
import org.keycloak.connections.mongo.impl.types.MapMapper;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class Update1_2_0_Beta1 extends Update {
@Override
public String getId() {
return "1.2.0.Beta1";
}
@Override
public void update(KeycloakSession session) {
deleteEntries("clientSessions");
deleteEntries("sessions");
convertSocialToIdFedRealms();
convertSocialToIdFedUsers();
addAccessCodeLoginTimeout();
addNewAdminRoles();
addDefaultProtocolMappers(session);
}
private void convertSocialToIdFedRealms() {
DBCollection realms = db.getCollection("realms");
DBCursor realmsCursor = realms.find();
try {
while (realmsCursor.hasNext()) {
BasicDBObject realm = (BasicDBObject) realmsCursor.next();
boolean updateProfileOnInitialSocialLogin = realm.getBoolean("updateProfileOnInitialSocialLogin");
BasicDBObject socialConfig = (BasicDBObject) realm.get("socialConfig");
BasicDBList identityProviders = (BasicDBList) realm.get("identityProviders");
if (identityProviders == null) {
identityProviders = new BasicDBList();
realm.put("identityProviders", identityProviders);
}
if (socialConfig != null) {
for (Map.Entry<String, Object> entry : socialConfig.entrySet()) {
if (entry.getKey().endsWith("###key")) {
String socialProviderId = entry.getKey().substring(0, entry.getKey().indexOf("###"));
String clientId = (String) entry.getValue();
String clientSecret = socialConfig.getString(socialProviderId + "###secret");
DBObject identityProviderConfig = new BasicDBObjectBuilder()
.add("clientId", clientId)
.add("clientSecret", clientSecret).get();
DBObject identityProvider = new BasicDBObjectBuilder()
.add("internalId", KeycloakModelUtils.generateId())
.add("providerId", socialProviderId)
.add("alias", socialProviderId)
.add("updateProfileFirstLogin", updateProfileOnInitialSocialLogin)
.add("enabled", true)
.add("storeToken", false)
.add("authenticateByDefault", false)
.add("config", identityProviderConfig).get();
identityProviders.add(identityProvider);
log.debugv("Converted social provider {0} to identity provider", socialProviderId);
}
}
}
// Remove obsolete keys from realm
realm.remove("social");
realm.remove("updateProfileOnInitialSocialLogin");
realm.remove("socialConfig");
// Update realm in DB now
realms.save(realm);
log.debugv("Social providers of realm {0} converted to identity providers", realm.get("_id"));
}
} finally {
realmsCursor.close();
}
}
private void convertSocialToIdFedUsers() {
DBCollection users = db.getCollection("users");
DBCursor usersCursor = users.find();
try {
while (usersCursor.hasNext()) {
BasicDBObject user = (BasicDBObject) usersCursor.next();
BasicDBList socialLinks = (BasicDBList) user.get("socialLinks");
if (socialLinks != null) {
BasicDBList federatedIdentities = (BasicDBList) user.get("federatedIdentities");
if (federatedIdentities == null) {
federatedIdentities = new BasicDBList();
user.put("federatedIdentities", federatedIdentities);
}
for (Object socialLinkObj : socialLinks) {
BasicDBObject socialLink = (BasicDBObject) socialLinkObj;
BasicDBObject idFedLink = new BasicDBObject();
idFedLink.put("userName", socialLink.get("socialUsername"));
idFedLink.put("userId", socialLink.get("socialUserId"));
idFedLink.put("identityProvider", socialLink.get("socialProvider"));
federatedIdentities.add(idFedLink);
}
// Remove obsolete keys and save user
user.remove("socialLinks");
users.save(user);
if (log.isTraceEnabled()) {
log.tracev("Social links of user {0} converted to identity links", user.get("_id"));
}
}
}
} finally {
usersCursor.close();
}
log.debug("Social links of users converted to identity links");
}
private void addAccessCodeLoginTimeout() {
DBCollection realms = db.getCollection("realms");
DBCursor realmsCursor = realms.find();
try {
while (realmsCursor.hasNext()) {
BasicDBObject realm = (BasicDBObject) realmsCursor.next();
realm.put("accessCodeLifespanLogin", 1800);
realms.save(realm);
}
} finally {
realmsCursor.close();
}
}
private void addNewAdminRoles() {
DBCollection realms = db.getCollection("realms");
String adminRealmName = Config.getAdminRealm();
DBCursor realmsCursor = realms.find();
try {
while (realmsCursor.hasNext()) {
BasicDBObject realm = (BasicDBObject) realmsCursor.next();
if (adminRealmName.equals(realm.get("name"))) {
addNewAdminRolesToMasterRealm(realm);
} else {
addNewAdminRolesToRealm(realm);
}
}
} finally {
realmsCursor.close();
}
}
private void addNewAdminRolesToMasterRealm(BasicDBObject adminRealm) {
DBCollection realms = db.getCollection("realms");
DBCollection applications = db.getCollection("applications");
DBCollection roles = db.getCollection("roles");
DBCursor realmsCursor = realms.find();
try {
while (realmsCursor.hasNext()) {
BasicDBObject currentRealm = (BasicDBObject) realmsCursor.next();
String masterAdminAppName = currentRealm.getString("name") + "-realm";
BasicDBObject masterAdminApp = (BasicDBObject) applications.findOne(new BasicDBObject().append("realmId", adminRealm.get("_id")).append("name", masterAdminAppName));
String viewIdProvidersRoleId = insertApplicationRole(roles, AdminRoles.VIEW_IDENTITY_PROVIDERS, masterAdminApp.getString("_id"));
String manageIdProvidersRoleId = insertApplicationRole(roles, AdminRoles.MANAGE_IDENTITY_PROVIDERS, masterAdminApp.getString("_id"));
BasicDBObject adminRole = (BasicDBObject) roles.findOne(new BasicDBObject().append("realmId", adminRealm.get("_id")).append("name", AdminRoles.ADMIN));
BasicDBList adminCompositeRoles = (BasicDBList) adminRole.get("compositeRoleIds");
adminCompositeRoles.add(viewIdProvidersRoleId);
adminCompositeRoles.add(manageIdProvidersRoleId);
roles.save(adminRole);
log.debugv("Added roles {0} and {1} to application {2}", AdminRoles.VIEW_IDENTITY_PROVIDERS, AdminRoles.MANAGE_IDENTITY_PROVIDERS, masterAdminAppName);
}
} finally {
realmsCursor.close();
}
}
private void addNewAdminRolesToRealm(BasicDBObject currentRealm) {
DBCollection applications = db.getCollection("applications");
DBCollection roles = db.getCollection("roles");
BasicDBObject adminApp = (BasicDBObject) applications.findOne(new BasicDBObject().append("realmId", currentRealm.get("_id")).append("name", "realm-management"));
String viewIdProvidersRoleId = insertApplicationRole(roles, AdminRoles.VIEW_IDENTITY_PROVIDERS, adminApp.getString("_id"));
String manageIdProvidersRoleId = insertApplicationRole(roles, AdminRoles.MANAGE_IDENTITY_PROVIDERS, adminApp.getString("_id"));
BasicDBObject adminRole = (BasicDBObject) roles.findOne(new BasicDBObject().append("applicationId", adminApp.get("_id")).append("name", AdminRoles.REALM_ADMIN));
BasicDBList adminCompositeRoles = (BasicDBList) adminRole.get("compositeRoleIds");
adminCompositeRoles.add(viewIdProvidersRoleId);
adminCompositeRoles.add(manageIdProvidersRoleId);
roles.save(adminRole);
log.debugv("Added roles {0} and {1} to application realm-management of realm {2}", AdminRoles.VIEW_IDENTITY_PROVIDERS, AdminRoles.MANAGE_IDENTITY_PROVIDERS, currentRealm.get("name"));
}
private void addDefaultProtocolMappers(KeycloakSession session) {
addDefaultMappers(session, db.getCollection("applications"));
addDefaultMappers(session, db.getCollection("oauthClients"));
}
private void addDefaultMappers(KeycloakSession session, DBCollection clients) {
DBCursor clientsCursor = clients.find();
try {
while (clientsCursor.hasNext()) {
BasicDBObject currentClient = (BasicDBObject) clientsCursor.next();
BasicDBList dbProtocolMappers = new BasicDBList();
currentClient.put("protocolMappers", dbProtocolMappers);
Object claimMask = currentClient.get("allowedClaimsMask");
MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class);
List<ProtocolMapperRepresentation> protocolMappers = migrationProvider.getMappersForClaimMask((Long) claimMask);
for (ProtocolMapperRepresentation protocolMapper : protocolMappers) {
BasicDBObject dbMapper = new BasicDBObject();
dbMapper.put("id", KeycloakModelUtils.generateId());
dbMapper.put("protocol", protocolMapper.getProtocol());
dbMapper.put("name", protocolMapper.getName());
dbMapper.put("consentRequired", protocolMapper.isConsentRequired());
dbMapper.put("consentText", protocolMapper.getConsentText());
dbMapper.put("protocolMapper", protocolMapper.getProtocolMapper());
Map<String, String> config = protocolMapper.getConfig();
BasicDBObject dbConfig = MapMapper.convertMap(config);
dbMapper.put("config", dbConfig);
dbProtocolMappers.add(dbMapper);
}
// Remove obsolete keys from client
currentClient.remove("allowedClaimsMask");
log.debugv("Added default mappers to application {1}", currentClient.get("name"));
clients.save(currentClient);
}
} finally {
clientsCursor.close();
}
}
}
@@ -1 +0,0 @@
org.keycloak.connections.mongo.updater.impl.DefaultMongoUpdaterProviderFactory
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -10,11 +10,9 @@ import org.keycloak.Config;
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.impl.MongoStoreImpl;
import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocationContext;
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
import org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.net.ssl.SSLSocketFactory;
import java.lang.reflect.Method;
import java.util.Collections;
@@ -28,18 +26,15 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
"org.keycloak.models.entities.IdentityProviderEntity",
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
"org.keycloak.models.entities.RequiredCredentialEntity",
"org.keycloak.models.entities.CredentialEntity",
"org.keycloak.models.entities.FederatedIdentityEntity",
"org.keycloak.models.entities.SocialLinkEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity",
"org.keycloak.models.sessions.mongo.entities.MongoUsernameLoginFailureEntity",
"org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity",
"org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity",
"org.keycloak.models.entities.UserFederationProviderEntity",
"org.keycloak.models.entities.ProtocolMapperEntity"
"org.keycloak.models.entities.UserFederationProviderEntity"
};
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
@@ -52,7 +47,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
@Override
public MongoConnectionProvider create(KeycloakSession session) {
lazyInit(session);
lazyInit();
TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore);
session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext));
@@ -64,13 +59,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
private void lazyInit(KeycloakSession session) {
private void lazyInit() {
if (client == null) {
synchronized (this) {
if (client == null) {
@@ -95,13 +84,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
String databaseSchema = config.get("databaseSchema");
if (databaseSchema != null) {
if (databaseSchema.equals("update")) {
MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
if (mongoUpdater == null) {
throw new RuntimeException("Can't update database: Mongo updater provider not found");
}
mongoUpdater.update(session, db);
new DefaultMongoUpdaterProvider().update(db);
} else {
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
}
@@ -150,10 +133,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
if (config.getLong("maxAutoConnectRetryTime") != null) {
builder.maxAutoConnectRetryTime(config.getLong("maxAutoConnectRetryTime"));
}
if(config.getBoolean("ssl", false)) {
builder.socketFactory(SSLSocketFactory.getDefault());
}
return builder.build();
}
@@ -18,7 +18,6 @@ import org.keycloak.connections.mongo.api.types.Mapper;
import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
import org.keycloak.connections.mongo.impl.types.BasicDBListMapper;
import org.keycloak.connections.mongo.impl.types.BasicDBListToSetMapper;
import org.keycloak.connections.mongo.impl.types.BasicDBObjectMapper;
import org.keycloak.connections.mongo.impl.types.BasicDBObjectToMapMapper;
import org.keycloak.connections.mongo.impl.types.EnumToStringMapper;
@@ -36,10 +35,8 @@ import org.keycloak.models.utils.reflection.PropertyQueries;
import java.util.ArrayList;
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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -74,10 +71,6 @@ public class MongoStoreImpl implements MongoStore {
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class));
mapperRegistry.addDBObjectMapper(new BasicDBListMapper(mapperRegistry));
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, HashSet.class));
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, Set.class));
mapperRegistry.addDBObjectMapper(new BasicDBListToSetMapper(mapperRegistry));
mapperRegistry.addAppObjectMapper(new MapMapper(HashMap.class));
mapperRegistry.addAppObjectMapper(new MapMapper(Map.class));
mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper());
@@ -6,7 +6,6 @@ import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -1,44 +0,0 @@
package org.keycloak.connections.mongo.impl.types;
import java.util.HashSet;
import java.util.Set;
import com.mongodb.BasicDBList;
import org.keycloak.connections.mongo.api.types.Mapper;
import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BasicDBListToSetMapper implements Mapper<BasicDBList, Set> {
private final MapperRegistry mapperRegistry;
public BasicDBListToSetMapper(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
@Override
public Set convertObject(MapperContext<BasicDBList, Set> context) {
BasicDBList dbList = context.getObjectToConvert();
Set<Object> appObjects = new HashSet<Object>();
Class<?> expectedListElementType = context.getGenericTypes().get(0);
for (Object dbObject : dbList) {
MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
appObjects.add(mapperRegistry.convertDBObjectToApplicationObject(newContext));
}
return appObjects;
}
@Override
public Class<? extends BasicDBList> getTypeOfObjectToConvert() {
return BasicDBList.class;
}
@Override
public Class<Set> getExpectedReturnType() {
return Set.class;
}
}
@@ -5,12 +5,12 @@ import org.keycloak.connections.mongo.api.types.Mapper;
import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
import java.util.Collection;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ListMapper<T extends Collection> implements Mapper<T, BasicDBList> {
public class ListMapper<T extends List> implements Mapper<T, BasicDBList> {
private final MapperRegistry mapperRegistry;
private final Class<T> listType;
@@ -25,13 +25,10 @@ public class MapMapper<T extends Map> implements Mapper<T, BasicDBObject> {
@Override
public BasicDBObject convertObject(MapperContext<T, BasicDBObject> context) {
T mapToConvert = context.getObjectToConvert();
return convertMap(mapToConvert);
}
T objectToConvert = context.getObjectToConvert();
public static BasicDBObject convertMap(Map mapToConvert) {
BasicDBObject dbObject = new BasicDBObject();
Set<Map.Entry> entries = mapToConvert.entrySet();
Set<Map.Entry> entries = objectToConvert.entrySet();
for (Map.Entry entry : entries) {
String key = (String)entry.getKey();
Object value = entry.getValue();
@@ -1,20 +1,23 @@
package org.keycloak.connections.mongo.updater.impl;
package org.keycloak.connections.mongo.updater;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.jboss.logging.Logger;
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
import org.keycloak.connections.mongo.updater.impl.updates.Update;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_0_0_Final;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_1_0_Beta1;
import org.keycloak.connections.mongo.updater.impl.updates.Update1_2_0_Beta1;
import org.keycloak.models.KeycloakSession;
import org.keycloak.connections.mongo.updater.updates.Update;
import org.keycloak.connections.mongo.updater.updates.Update1_0_0_Final;
import org.keycloak.connections.mongo.updater.updates.Update1_1_0_Beta1;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -27,12 +30,11 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
private Class<? extends Update>[] updates = new Class[]{
Update1_0_0_Final.class,
Update1_1_0_Beta1.class,
Update1_2_0_Beta1.class
Update1_1_0_Beta1.class
};
@Override
public void update(KeycloakSession session, DB db) {
public void update(DB db) {
log.debug("Starting database update");
try {
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
@@ -77,19 +79,17 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
u.setLog(log);
u.setDb(db);
u.update(session);
u.update();
createLog(changeLog, u, ++order);
log.debugv("Completed updates for {0}", u.getId());
}
log.debug("Completed database update");
} else {
log.debug("Skip database update. Database is already up to date");
}
} catch (Exception e) {
throw new RuntimeException("Failed to update database", e);
}
log.debug("Completed database update");
}
private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
@@ -1,10 +1,7 @@
package org.keycloak.connections.mongo.updater.impl;
package org.keycloak.connections.mongo.updater;
import org.keycloak.Config;
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
import org.keycloak.connections.mongo.updater.MongoUpdaterProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -20,10 +17,6 @@ public class DefaultMongoUpdaterProviderFactory implements MongoUpdaterProviderF
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@@ -1,7 +1,6 @@
package org.keycloak.connections.mongo.updater;
import com.mongodb.DB;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
/**
@@ -9,6 +8,6 @@ import org.keycloak.provider.Provider;
*/
public interface MongoUpdaterProvider extends Provider {
public void update(KeycloakSession session, DB db);
public void update(DB db);
}
@@ -1,11 +1,9 @@
package org.keycloak.connections.mongo.updater.impl.updates;
package org.keycloak.connections.mongo.updater.updates;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Arrays;
@@ -20,7 +18,7 @@ public abstract class Update {
public abstract String getId();
public abstract void update(KeycloakSession session) throws ClassNotFoundException;
public abstract void update() throws ClassNotFoundException;
protected DBCollection createCollection(String name) {
if (db.collectionExists(name)) {
@@ -53,17 +51,6 @@ public abstract class Update {
log.debugv("Deleted entries from {0}", collection);
}
protected String insertApplicationRole(DBCollection roles, String roleName, String applicationId) {
BasicDBObject role = new BasicDBObject();
String roleId = KeycloakModelUtils.generateId();
role.append("_id", roleId);
role.append("name", roleName);
role.append("applicationId", applicationId);
role.append("nameIndex", applicationId + "//" + roleName);
roles.insert(role);
return roleId;
}
public void setLog(Logger log) {
this.log = log;
}
@@ -1,9 +1,8 @@
package org.keycloak.connections.mongo.updater.impl.updates;
package org.keycloak.connections.mongo.updater.updates;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import org.keycloak.connections.mongo.updater.impl.DefaultMongoUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -16,7 +15,7 @@ public class Update1_0_0_Final extends Update {
}
@Override
public void update(KeycloakSession session) throws ClassNotFoundException {
public void update() throws ClassNotFoundException {
DBCollection realmsCollection = db.getCollection("realms");
realmsCollection.ensureIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
@@ -1,12 +1,13 @@
package org.keycloak.connections.mongo.updater.impl.updates;
package org.keycloak.connections.mongo.updater.updates;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Arrays;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -18,7 +19,7 @@ public class Update1_1_0_Beta1 extends Update {
}
@Override
public void update(KeycloakSession session) {
public void update() {
deleteEntries("clientSessions");
deleteEntries("sessions");
@@ -1,2 +1 @@
org.keycloak.connections.mongo.MongoConnectionSpi
org.keycloak.connections.mongo.updater.MongoUpdaterSpi
org.keycloak.connections.mongo.MongoConnectionSpi
+1 -3
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
</parent>
<name>Connections Parent</name>
<description/>
@@ -17,8 +17,6 @@
<module>jpa-liquibase</module>
<module>infinispan</module>
<module>mongo</module>
<module>file</module>
<module>mongo-update</module>
</modules>
<build>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+2 -13
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1</version>
<version>1.1.1.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -28,12 +28,7 @@
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<artifactId>bcprov-jdk16</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -56,12 +51,6 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>3.9</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
@@ -18,7 +18,8 @@ public class AbstractOAuthClient {
protected String clientId;
protected Map<String, String> credentials;
protected String authUrl;
protected String tokenUrl;
protected String codeUrl;
protected String refreshUrl;
protected RelativeUrlsUsed relativeUrlsUsed;
protected String scope;
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
@@ -53,12 +54,20 @@ public class AbstractOAuthClient {
this.authUrl = authUrl;
}
public String getTokenUrl() {
return tokenUrl;
public String getCodeUrl() {
return codeUrl;
}
public void setTokenUrl(String tokenUrl) {
this.tokenUrl = tokenUrl;
public void setCodeUrl(String codeUrl) {
this.codeUrl = codeUrl;
}
public String getRefreshUrl() {
return refreshUrl;
}
public void setRefreshUrl(String refreshUrl) {
this.refreshUrl = refreshUrl;
}
public String getScope() {
@@ -50,7 +50,7 @@ public class KeycloakSecurityContext implements Serializable {
public String getRealm() {
// Assumption that issuer contains realm name
return token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1);
return token.getIssuer();
}
// SERIALIZATION
@@ -25,10 +25,6 @@ public interface OAuth2Constants {
String REFRESH_TOKEN = "refresh_token";
String AUTHORIZATION_CODE = "authorization_code";
String PASSWORD = "password";
}
@@ -12,11 +12,11 @@ import java.security.PublicKey;
* @version $Revision: 1 $
*/
public class RSATokenVerifier {
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl) throws VerificationException {
return verifyToken(tokenString, realmKey, realmUrl, true);
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realm) throws VerificationException {
return verifyToken(tokenString, realmKey, realm, true);
}
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive) throws VerificationException {
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realm, boolean checkActive) throws VerificationException {
JWSInput input = null;
try {
input = new JWSInput(tokenString);
@@ -35,10 +35,7 @@ public class RSATokenVerifier {
if (user == null) {
throw new VerificationException("Token user was null.");
}
if (realmUrl == null) {
throw new VerificationException("Realm URL is null. Make sure to add auth-server-url to the configuration of your adapter!");
}
if (!realmUrl.equals(token.getIssuer())) {
if (!realm.equals(token.getIssuer())) {
throw new VerificationException("Token audience doesn't match domain.");
}
@@ -12,7 +12,6 @@ import java.util.Properties;
*/
public class Version {
public static String VERSION;
public static String RESOURCES_VERSION;
public static String BUILD_TIME;
public static final String UNKNOWN = "UNKNOWN";
public static final Version SINGLETON;
@@ -27,7 +26,6 @@ public class Version {
props.load(is);
VERSION = props.getProperty("version");
BUILD_TIME = props.getProperty("build-time");
RESOURCES_VERSION = VERSION.toLowerCase();
} catch (IOException e) {
VERSION=UNKNOWN;
BUILD_TIME=UNKNOWN;
@@ -29,7 +29,4 @@ public interface AdapterConstants {
// Cookie used on adapter side to store token info. Used only when tokenStore is 'COOKIE'
public static final String KEYCLOAK_ADAPTER_STATE_COOKIE = "KEYCLOAK_ADAPTER_STATE";
// Request parameter used to specify the identifier of the identity provider that should be used to authenticate an user
String KC_IDP_HINT = "kc_idp_hint";
}
@@ -1,61 +0,0 @@
package org.keycloak.constants;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class KerberosConstants {
/**
* Value of HTTP Headers "WWW-Authenticate" or "Authorization" used for SPNEGO/Kerberos
**/
public static final String NEGOTIATE = "Negotiate";
/**
* OID of SPNEGO mechanism. See http://www.oid-info.com/get/1.3.6.1.5.5.2
*/
public static final String SPNEGO_OID = "1.3.6.1.5.5.2";
/**
* OID of Kerberos v5 mechanism. See http://www.oid-info.com/get/1.2.840.113554.1.2.2
*/
public static final String KRB5_OID = "1.2.840.113554.1.2.2";
/**
* OID of Kerberos v5 name. See http://www.oid-info.com/get/1.2.840.113554.1.2.2.1
*/
public static final String KRB5_NAME_OID = "1.2.840.113554.1.2.2.1";
/**
* Configuration federation provider model attributes.
*/
public static final String ALLOW_KERBEROS_AUTHENTICATION = "allowKerberosAuthentication";
public static final String KERBEROS_REALM = "kerberosRealm";
public static final String SERVER_PRINCIPAL = "serverPrincipal";
public static final String KEYTAB = "keyTab";
public static final String DEBUG = "debug";
public static final String ALLOW_PASSWORD_AUTHENTICATION = "allowPasswordAuthentication";
public static final String UPDATE_PROFILE_FIRST_LOGIN = "updateProfileFirstLogin";
public static final String USE_KERBEROS_FOR_PASSWORD_AUTHENTICATION = "useKerberosForPasswordAuthentication";
/**
* Internal attribute used in "state" map . Contains token to be passed in HTTP Response back to browser to continue handshake
*/
public static final String RESPONSE_TOKEN = "SpnegoResponseToken";
/**
* Internal attribute used in "userSession.note" map and in accessToken claims . Contains credential from SPNEGO/Kerberos successful authentication
*/
public static final String GSS_DELEGATION_CREDENTIAL = "gss_delegation_credential";
/**
* Display name for the above in admin console and consent screens
*/
public static final String GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME = "gss delegation credential";
}
@@ -6,9 +6,11 @@ package org.keycloak.constants;
*/
public interface ServiceUrlConstants {
public static final String AUTH_PATH = "/realms/{realm-name}/protocol/openid-connect/auth";
public static final String TOKEN_PATH = "/realms/{realm-name}/protocol/openid-connect/token";
public static final String TOKEN_SERVICE_LOGIN_PATH = "/realms/{realm-name}/protocol/openid-connect/login";
public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/realms/{realm-name}/protocol/openid-connect/access/codes";
public static final String TOKEN_SERVICE_REFRESH_PATH = "/realms/{realm-name}/protocol/openid-connect/refresh";
public static final String TOKEN_SERVICE_LOGOUT_PATH = "/realms/{realm-name}/protocol/openid-connect/logout";
public static final String TOKEN_SERVICE_DIRECT_GRANT_PATH = "/realms/{realm-name}/protocol/openid-connect/grants/access";
public static final String ACCOUNT_SERVICE_PATH = "/realms/{realm-name}/account";
public static final String REALM_INFO_PATH = "/realms/{realm-name}";
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
@@ -1,84 +0,0 @@
package org.keycloak.jose.jwk;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
import org.codehaus.jackson.annotate.JsonProperty;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWK {
public static final String KEY_ID = "kid";
public static final String KEY_TYPE = "kty";
public static final String ALGORITHM = "alg";
public static final String PUBLIC_KEY_USE = "use";
public static final String SIG_USE = "sig";
public static final String ENCRYPTION_USE = "enc";
@JsonProperty(KEY_ID)
private String keyId;
@JsonProperty(KEY_TYPE)
private String keyType;
@JsonProperty(ALGORITHM)
private String algorithm;
@JsonProperty(PUBLIC_KEY_USE)
private String publicKeyUse;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
public String getKeyId() {
return keyId;
}
public void setKeyId(String keyId) {
this.keyId = keyId;
}
public String getKeyType() {
return keyType;
}
public void setKeyType(String keyType) {
this.keyType = keyType;
}
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getPublicKeyUse() {
return publicKeyUse;
}
public void setPublicKeyUse(String publicKeyUse) {
this.publicKeyUse = publicKeyUse;
}
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;
}
@JsonAnySetter
public void setOtherClaims(String name, Object value) {
otherClaims.put(name, value);
}
}
@@ -1,79 +0,0 @@
package org.keycloak.jose.jwk;
import org.keycloak.util.Base64Url;
import java.math.BigInteger;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKBuilder {
public static final String DEFAULT_PUBLIC_KEY_USE = "sig";
public static final String DEFAULT_MESSAGE_DIGEST = "SHA-256";
private JWKBuilder() {
}
public static JWKBuilder create() {
return new JWKBuilder();
}
public JWK rs256(PublicKey key) {
RSAPublicKey rsaKey = (RSAPublicKey) key;
RSAPublicJWK k = new RSAPublicJWK();
k.setKeyId(createKeyId(key));
k.setKeyType(RSAPublicJWK.RSA);
k.setAlgorithm(RSAPublicJWK.RS256);
k.setPublicKeyUse(DEFAULT_PUBLIC_KEY_USE);
k.setModulus(Base64Url.encode(toIntegerBytes(rsaKey.getModulus())));
k.setPublicExponent(Base64Url.encode(toIntegerBytes(rsaKey.getPublicExponent())));
return k;
}
private String createKeyId(Key key) {
try {
return Base64Url.encode(MessageDigest.getInstance(DEFAULT_MESSAGE_DIGEST).digest(key.getEncoded()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* Copied from org.apache.commons.codec.binary.Base64
*/
private static byte[] toIntegerBytes(final BigInteger bigInt) {
int bitlen = bigInt.bitLength();
// round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
final byte[] bigBytes = bigInt.toByteArray();
if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if ((bigInt.bitLength() % 8) == 0) {
startSrc = 1;
len--;
}
final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
final byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
return resizedBytes;
}
}
@@ -1,66 +0,0 @@
package org.keycloak.jose.jwk;
import org.codehaus.jackson.type.TypeReference;
import org.keycloak.util.Base64Url;
import org.keycloak.util.JsonSerialization;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JWKParser {
private static TypeReference<Map<String,String>> typeRef = new TypeReference<Map<String,String>>() {};
private JWK jwk;
private JWKParser() {
}
public JWKParser(JWK jwk) {
this.jwk = jwk;
}
public static JWKParser create() {
return new JWKParser();
}
public static JWKParser create(JWK jwk) {
return new JWKParser(jwk);
}
public JWKParser parse(String jwk) {
try {
this.jwk = JsonSerialization.mapper.readValue(jwk, JWK.class);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public JWK getJwk() {
return jwk;
}
public PublicKey toPublicKey() {
String algorithm = jwk.getKeyType();
if (RSAPublicJWK.RSA.equals(algorithm)) {
BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
try {
return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("Unsupported algorithm " + algorithm);
}
}
}
@@ -1,38 +0,0 @@
package org.keycloak.jose.jwk;
import org.codehaus.jackson.annotate.JsonProperty;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RSAPublicJWK extends JWK {
public static final String RSA = "RSA";
public static final String RS256 = "RS256";
public static final String MODULUS = "n";
public static final String PUBLIC_EXPONENT = "e";
@JsonProperty(MODULUS)
private String modulus;
@JsonProperty("e")
private String publicExponent;
public String getModulus() {
return modulus;
}
public void setModulus(String modulus) {
this.modulus = modulus;
}
public String getPublicExponent() {
return publicExponent;
}
public void setPublicExponent(String publicExponent) {
this.publicExponent = publicExponent;
}
}
@@ -1,32 +1,18 @@
package org.keycloak.jose.jws;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.jose.jws.crypto.SignatureProvider;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public enum Algorithm {
none(null),
HS256(null),
HS384(null),
HS512(null),
RS256(new RSAProvider()),
RS384(new RSAProvider()),
RS512(new RSAProvider()),
ES256(null),
ES384(null),
ES512(null)
;
private SignatureProvider provider;
Algorithm(SignatureProvider provider) {
this.provider = provider;
}
public SignatureProvider getProvider() {
return provider;
}
none,
HS256,
HS384,
HS512,
RS256,
RS384,
RS512,
ES256,
ES384,
ES512
}
@@ -21,9 +21,6 @@ public class JWSHeader implements Serializable {
@JsonProperty("cty")
private String contentType;
@JsonProperty("kid")
private String keyId;
public JWSHeader() {
}
@@ -45,9 +42,6 @@ public class JWSHeader implements Serializable {
return contentType;
}
public String getKeyId() {
return keyId;
}
private static final ObjectMapper mapper = new ObjectMapper();
@@ -2,7 +2,6 @@ package org.keycloak.jose.jws;
import org.keycloak.util.Base64Url;
import org.keycloak.util.JsonSerialization;
import static org.keycloak.jose.jws.Algorithm.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -74,13 +73,6 @@ public class JWSInput {
return signature;
}
public boolean verify(String key) {
if (header.getAlgorithm().getProvider() == null) {
throw new RuntimeException("signing algorithm not supported");
}
return header.getAlgorithm().getProvider().verify(this, key);
}
public <T> T readJsonContent(Class<T> type) throws IOException {
return JsonSerialization.readValue(content, type);
}
@@ -14,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HMACProvider implements SignatureProvider {
public class HMACProvider {
private static String getJavaAlgorithm(Algorithm alg) {
switch (alg) {
case HS256:
@@ -82,8 +82,5 @@ public class HMACProvider implements SignatureProvider {
}
}
@Override
public boolean verify(JWSInput input, String key) {
return false;
}
}
@@ -3,18 +3,16 @@ package org.keycloak.jose.jws.crypto;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.util.PemUtils;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RSAProvider implements SignatureProvider {
public class RSAProvider {
public static String getJavaAlgorithm(Algorithm alg) {
switch (alg) {
case RS256:
@@ -47,16 +45,6 @@ public class RSAProvider implements SignatureProvider {
}
}
public static boolean verifyViaCertificate(JWSInput input, String cert) {
X509Certificate certificate = null;
try {
certificate = PemUtils.decodeCertificate(cert);
} catch (Exception e) {
throw new RuntimeException(e);
}
return verify(input, certificate.getPublicKey());
}
public static boolean verify(JWSInput input, PublicKey publicKey) {
try {
Signature verifier = getSignature(input.getHeader().getAlgorithm());
@@ -69,10 +57,5 @@ public class RSAProvider implements SignatureProvider {
}
@Override
public boolean verify(JWSInput input, String key) {
return verifyViaCertificate(input, key);
}
}
@@ -1,11 +0,0 @@
package org.keycloak.jose.jws.crypto;
import org.keycloak.jose.jws.JWSInput;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface SignatureProvider {
boolean verify(JWSInput input, String key);
}
@@ -64,9 +64,6 @@ public class AccessToken extends IDToken {
}
}
@JsonProperty("client_session")
protected String clientSession;
@JsonProperty("trusted-certs")
protected Set<String> trustedCertificates;
@@ -120,21 +117,10 @@ public class AccessToken extends IDToken {
return resourceAccess.get(resource);
}
public String getClientSession() {
return clientSession;
}
public Access addAccess(String service) {
Access access = resourceAccess.get(service);
if (access != null) return access;
access = new Access();
resourceAccess.put(service, access);
return access;
}
public AccessToken clientSession(String session) {
this.clientSession = session;
return this;
Access token = new Access();
resourceAccess.put(service, token);
return token;
}
@Override
@@ -1,12 +1,7 @@
package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
import org.codehaus.jackson.annotate.JsonProperty;
import java.util.HashMap;
import java.util.Map;
/**
* OAuth 2.0 Access Token Response json
*
@@ -38,10 +33,6 @@ public class AccessTokenResponse {
@JsonProperty("session-state")
protected String sessionState;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
public String getToken() {
return token;
}
@@ -105,15 +96,4 @@ public class AccessTokenResponse {
public void setSessionState(String sessionState) {
this.sessionState = sessionState;
}
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;
}
@JsonAnySetter
public void setOtherClaims(String name, Object value) {
otherClaims.put(name, value);
}
}
@@ -1,76 +0,0 @@
package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonProperty;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AddressClaimSet {
@JsonProperty("formatted")
protected String formattedAddress;
@JsonProperty("street_address")
protected String streetAddress;
@JsonProperty("locality")
protected String locality;
@JsonProperty("region")
protected String region;
@JsonProperty("postal_code")
protected String postalCode;
@JsonProperty("country")
protected String country;
public String getFormattedAddress() {
return this.formattedAddress;
}
public void setFormattedAddress(String formattedAddress) {
this.formattedAddress = formattedAddress;
}
public String getStreetAddress() {
return this.streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
public String getLocality() {
return this.locality;
}
public void setLocality(String locality) {
this.locality = locality;
}
public String getRegion() {
return this.region;
}
public void setRegion(String region) {
this.region = region;
}
public String getPostalCode() {
return this.postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
}
@@ -1,26 +1,15 @@
package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.annotate.JsonUnwrapped;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class IDToken extends JsonWebToken {
// NOTE!!! WE used to use @JsonUnwrapped on a UserClaimSet object. This screws up otherClaims and the won't work
// anymore. So don't have any @JsonUnwrapped!
@JsonProperty("nonce")
protected String nonce;
@JsonProperty("session_state")
protected String sessionState;
@JsonProperty("name")
protected String name;
@@ -73,15 +62,34 @@ public class IDToken extends JsonWebToken {
protected Boolean phoneNumberVerified;
@JsonProperty("address")
protected AddressClaimSet address;
protected String address;
@JsonProperty("updated_at")
protected Long updatedAt;
@JsonProperty("formatted")
protected String formattedAddress;
@JsonProperty("street_address")
protected String streetAddress;
@JsonProperty("locality")
protected String locality;
@JsonProperty("region")
protected String region;
@JsonProperty("postal_code")
protected String postalCode;
@JsonProperty("country")
protected String country;
@JsonProperty("claims_locales")
protected String claimsLocales;
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
@JsonProperty("session_state")
protected String sessionState;
public String getNonce() {
return nonce;
@@ -91,16 +99,8 @@ public class IDToken extends JsonWebToken {
this.nonce = nonce;
}
public String getSessionState() {
return sessionState;
}
public void setSessionState(String sessionState) {
this.sessionState = sessionState;
}
public String getName() {
return this.name;
return name;
}
public void setName(String name) {
@@ -108,7 +108,7 @@ public class IDToken extends JsonWebToken {
}
public String getGivenName() {
return this.givenName;
return givenName;
}
public void setGivenName(String givenName) {
@@ -116,7 +116,7 @@ public class IDToken extends JsonWebToken {
}
public String getFamilyName() {
return this.familyName;
return familyName;
}
public void setFamilyName(String familyName) {
@@ -124,7 +124,7 @@ public class IDToken extends JsonWebToken {
}
public String getMiddleName() {
return this.middleName;
return middleName;
}
public void setMiddleName(String middleName) {
@@ -132,7 +132,7 @@ public class IDToken extends JsonWebToken {
}
public String getNickName() {
return this.nickName;
return nickName;
}
public void setNickName(String nickName) {
@@ -140,7 +140,7 @@ public class IDToken extends JsonWebToken {
}
public String getPreferredUsername() {
return this.preferredUsername;
return preferredUsername;
}
public void setPreferredUsername(String preferredUsername) {
@@ -148,7 +148,7 @@ public class IDToken extends JsonWebToken {
}
public String getProfile() {
return this.profile;
return profile;
}
public void setProfile(String profile) {
@@ -156,7 +156,7 @@ public class IDToken extends JsonWebToken {
}
public String getPicture() {
return this.picture;
return picture;
}
public void setPicture(String picture) {
@@ -164,7 +164,7 @@ public class IDToken extends JsonWebToken {
}
public String getWebsite() {
return this.website;
return website;
}
public void setWebsite(String website) {
@@ -172,7 +172,7 @@ public class IDToken extends JsonWebToken {
}
public String getEmail() {
return this.email;
return email;
}
public void setEmail(String email) {
@@ -180,7 +180,7 @@ public class IDToken extends JsonWebToken {
}
public Boolean getEmailVerified() {
return this.emailVerified;
return emailVerified;
}
public void setEmailVerified(Boolean emailVerified) {
@@ -188,7 +188,7 @@ public class IDToken extends JsonWebToken {
}
public String getGender() {
return this.gender;
return gender;
}
public void setGender(String gender) {
@@ -196,7 +196,7 @@ public class IDToken extends JsonWebToken {
}
public String getBirthdate() {
return this.birthdate;
return birthdate;
}
public void setBirthdate(String birthdate) {
@@ -204,7 +204,7 @@ public class IDToken extends JsonWebToken {
}
public String getZoneinfo() {
return this.zoneinfo;
return zoneinfo;
}
public void setZoneinfo(String zoneinfo) {
@@ -212,7 +212,7 @@ public class IDToken extends JsonWebToken {
}
public String getLocale() {
return this.locale;
return locale;
}
public void setLocale(String locale) {
@@ -220,7 +220,7 @@ public class IDToken extends JsonWebToken {
}
public String getPhoneNumber() {
return this.phoneNumber;
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
@@ -228,49 +228,90 @@ public class IDToken extends JsonWebToken {
}
public Boolean getPhoneNumberVerified() {
return this.phoneNumberVerified;
return phoneNumberVerified;
}
public void setPhoneNumberVerified(Boolean phoneNumberVerified) {
this.phoneNumberVerified = phoneNumberVerified;
}
public AddressClaimSet getAddress() {
public String getAddress() {
return address;
}
public void setAddress(AddressClaimSet address) {
public void setAddress(String address) {
this.address = address;
}
public Long getUpdatedAt() {
return this.updatedAt;
return updatedAt;
}
public void setUpdatedAt(Long updatedAt) {
this.updatedAt = updatedAt;
}
public String getFormattedAddress() {
return formattedAddress;
}
public void setFormattedAddress(String formattedAddress) {
this.formattedAddress = formattedAddress;
}
public String getStreetAddress() {
return streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
public String getLocality() {
return locality;
}
public void setLocality(String locality) {
this.locality = locality;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getClaimsLocales() {
return this.claimsLocales;
return claimsLocales;
}
public void setClaimsLocales(String claimsLocales) {
this.claimsLocales = claimsLocales;
}
/**
* This is a map of any other claims and data that might be in the IDToken. Could be custom claims set up by the auth server
*
* @return
*/
@JsonAnyGetter
public Map<String, Object> getOtherClaims() {
return otherClaims;
public String getSessionState() {
return sessionState;
}
@JsonAnySetter
public void setOtherClaims(String name, Object value) {
otherClaims.put(name, value);
public void setSessionState(String sessionState) {
this.sessionState = sessionState;
}
}
@@ -127,10 +127,6 @@ public class JsonWebToken implements Serializable {
return this;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getType() {
return type;
}
@@ -1,7 +1,5 @@
package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonProperty;
import java.util.HashMap;
import java.util.Map;
@@ -10,9 +8,7 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class RefreshToken extends AccessToken {
private RefreshToken() {
public RefreshToken() {
type("REFRESH");
}
@@ -24,7 +20,6 @@ public class RefreshToken extends AccessToken {
*/
public RefreshToken(AccessToken token) {
this();
this.clientSession = token.getClientSession();
this.issuer = token.issuer;
this.subject = token.subject;
this.issuedFor = token.issuedFor;
@@ -1,264 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonProperty;
/**
* @author pedroigor
*/
public class UserInfo {
@JsonProperty("sub")
protected String sub;
@JsonProperty("name")
protected String name;
@JsonProperty("given_name")
protected String givenName;
@JsonProperty("family_name")
protected String familyName;
@JsonProperty("middle_name")
protected String middleName;
@JsonProperty("nickname")
protected String nickName;
@JsonProperty("preferred_username")
protected String preferredUsername;
@JsonProperty("profile")
protected String profile;
@JsonProperty("picture")
protected String picture;
@JsonProperty("website")
protected String website;
@JsonProperty("email")
protected String email;
@JsonProperty("email_verified")
protected Boolean emailVerified;
@JsonProperty("gender")
protected String gender;
@JsonProperty("birthdate")
protected String birthdate;
@JsonProperty("zoneinfo")
protected String zoneinfo;
@JsonProperty("locale")
protected String locale;
@JsonProperty("phone_number")
protected String phoneNumber;
@JsonProperty("phone_number_verified")
protected Boolean phoneNumberVerified;
@JsonProperty("address")
protected AddressClaimSet address;
@JsonProperty("updated_at")
protected Long updatedAt;
@JsonProperty("claims_locales")
protected String claimsLocales;
public String getSubject() {
return this.sub;
}
public void setSubject(String subject) {
this.sub = subject;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getGivenName() {
return this.givenName;
}
public void setGivenName(String givenName) {
this.givenName = givenName;
}
public String getFamilyName() {
return this.familyName;
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
}
public String getMiddleName() {
return this.middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getNickName() {
return this.nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getPreferredUsername() {
return this.preferredUsername;
}
public void setPreferredUsername(String preferredUsername) {
this.preferredUsername = preferredUsername;
}
public String getProfile() {
return this.profile;
}
public void setProfile(String profile) {
this.profile = profile;
}
public String getPicture() {
return this.picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
public String getWebsite() {
return this.website;
}
public void setWebsite(String website) {
this.website = website;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public Boolean getEmailVerified() {
return this.emailVerified;
}
public void setEmailVerified(Boolean emailVerified) {
this.emailVerified = emailVerified;
}
public String getGender() {
return this.gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getBirthdate() {
return this.birthdate;
}
public void setBirthdate(String birthdate) {
this.birthdate = birthdate;
}
public String getZoneinfo() {
return this.zoneinfo;
}
public void setZoneinfo(String zoneinfo) {
this.zoneinfo = zoneinfo;
}
public String getLocale() {
return this.locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public String getPhoneNumber() {
return this.phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Boolean getPhoneNumberVerified() {
return this.phoneNumberVerified;
}
public void setPhoneNumberVerified(Boolean phoneNumberVerified) {
this.phoneNumberVerified = phoneNumberVerified;
}
public AddressClaimSet getAddress() {
return address;
}
public void setAddress(AddressClaimSet address) {
this.address = address;
}
public Long getUpdatedAt() {
return this.updatedAt;
}
public void setUpdatedAt(Long updatedAt) {
this.updatedAt = updatedAt;
}
public String getSub() {
return this.sub;
}
public void setSub(String sub) {
this.sub = sub;
}
public String getClaimsLocales() {
return this.claimsLocales;
}
public void setClaimsLocales(String claimsLocales) {
this.claimsLocales = claimsLocales;
}
}
@@ -10,16 +10,14 @@ public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT";
protected List<String> adapterSessionIds;
protected int notBefore;
protected List<String> keycloakSessionIds;
public LogoutAction() {
}
public LogoutAction(String id, int expiration, String resource, List<String> adapterSessionIds, int notBefore, List<String> keycloakSessionIds) {
public LogoutAction(String id, int expiration, String resource, List<String> adapterSessionIds, int notBefore) {
super(id, expiration, resource, LOGOUT);
this.adapterSessionIds = adapterSessionIds;
this.notBefore = notBefore;
this.keycloakSessionIds = keycloakSessionIds;
}
@@ -35,14 +33,6 @@ public class LogoutAction extends AdminAction {
return adapterSessionIds;
}
public List<String> getKeycloakSessionIds() {
return keycloakSessionIds;
}
public void setKeycloakSessionIds(List<String> keycloakSessionIds) {
this.keycloakSessionIds = keycloakSessionIds;
}
@Override
public boolean validate() {
return LOGOUT.equals(action);
@@ -18,19 +18,15 @@ public class ApplicationRepresentation {
protected String[] defaultRoles;
protected List<String> redirectUris;
protected List<String> webOrigins;
@Deprecated
protected ClaimRepresentation claims;
protected Integer notBefore;
protected Boolean bearerOnly;
protected Boolean publicClient;
protected Boolean frontchannelLogout;
protected String protocol;
protected Map<String, String> attributes;
protected Boolean fullScopeAllowed;
protected Integer nodeReRegistrationTimeout;
protected Map<String, Integer> registeredNodes;
protected List<ClientIdentityProviderMappingRepresentation> identityProviders;
protected List<ProtocolMapperRepresentation> protocolMappers;
public String getId() {
return id;
@@ -183,28 +179,4 @@ public class ApplicationRepresentation {
public void setRegisteredNodes(Map<String, Integer> registeredNodes) {
this.registeredNodes = registeredNodes;
}
public Boolean isFrontchannelLogout() {
return frontchannelLogout;
}
public void setFrontchannelLogout(Boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
public List<ClientIdentityProviderMappingRepresentation> getIdentityProviders() {
return this.identityProviders;
}
public void setIdentityProviders(List<ClientIdentityProviderMappingRepresentation> identityProviders) {
this.identityProviders = identityProviders;
}
public List<ProtocolMapperRepresentation> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}
@@ -1,43 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.idm;
/**
* @author pedroigor
*/
public class ClientIdentityProviderMappingRepresentation {
protected String id;
protected boolean retrieveToken;
public String getId() {
return this.id;
}
public void setId(String identityProviderId) {
this.id = identityProviderId;
}
public boolean isRetrieveToken() {
return this.retrieveToken;
}
public void setRetrieveToken(boolean retrieveToken) {
this.retrieveToken = retrieveToken;
}
}
@@ -10,7 +10,6 @@ public class CredentialRepresentation {
public static final String PASSWORD_TOKEN = "password-token";
public static final String TOTP = "totp";
public static final String CLIENT_CERT = "cert";
public static final String KERBEROS = "kerberos";
protected String type;
protected String device;
@@ -1,35 +0,0 @@
package org.keycloak.representations.idm;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FederatedIdentityRepresentation {
protected String identityProvider;
protected String userId;
protected String userName;
public String getIdentityProvider() {
return identityProvider;
}
public void setIdentityProvider(String identityProvider) {
this.identityProvider = identityProvider;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
Loaded 100 of 1470 files, more files were not shown because too many files have changed in this diff. Show more