Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ec0409a46 | ||
|
|
cd5e191b18 | ||
|
|
a14b3753da | ||
|
|
41b8701d51 | ||
|
|
d84e7eb38b | ||
|
|
d40b256557 | ||
|
|
5304e197e5 | ||
|
|
623e28b9c2 | ||
|
|
274bc4e33a | ||
|
|
42b44ed3b0 | ||
|
|
b0c99e6111 | ||
|
|
fe73feee34 | ||
|
|
629d3ce4aa | ||
|
|
ed6c290bbc | ||
|
|
9dde3f60d6 | ||
|
|
1bcc8c9110 | ||
|
|
bb132e1aa0 | ||
|
|
5e4b843eb7 | ||
|
|
85cc63c7ce | ||
|
|
a697f4bd41 | ||
|
|
7e03c74bc3 | ||
|
|
67f7bcec99 | ||
|
|
267b80c2fa | ||
|
|
66e848710e | ||
|
|
93f48ecb6c | ||
|
|
ecace349f5 | ||
|
|
4a5b313816 | ||
|
|
c79506a34f | ||
|
|
5e97051c07 | ||
|
|
562aa24ebf | ||
|
|
4a66687304 | ||
|
|
42c2066fd8 | ||
|
|
161b3293dc | ||
|
|
a9608904d0 | ||
|
|
1b9358c4c6 | ||
|
|
70ff6c7209 | ||
|
|
3133e30265 | ||
|
|
d0c2e80949 | ||
|
|
89e1937501 |
No files matched your search
@@ -9,12 +9,6 @@
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
catalog.xml
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
language: java
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
sudo: false
|
||||
|
||||
after_script: 'misc/.travis/after.sh'
|
||||
@@ -5,14 +5,14 @@ Please visit [http://keycloak.org](http://keycloak.org) for more information on
|
||||
and video tutorials.
|
||||
|
||||
Keycloak is an SSO Service for web apps and REST services.
|
||||
It can be used for social applications as well as enterprise applications. It is based on OpenID Connect with support for SAML 2.0 as well.
|
||||
It can be used for social applications as well as enterprise applications. It is based on OpenID Connect with SAML support in the works.
|
||||
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.
|
||||
* Password and TOTP support (via Google Authenticator). Client cert auth coming soon.
|
||||
* User session management from both admin and user perspective
|
||||
* Customizable themes for user facing pages: login, grant pages, account management, emails, and admin console all customizable!
|
||||
* OAuth Bearer token auth for REST Services
|
||||
@@ -24,16 +24,12 @@ Here's some of the features:
|
||||
* Completely centrally managed user and role mapping metadata. Minimal configuration at the application side
|
||||
* Admin Console for managing users, roles, role mappings, applications, user sessions, allowed CORS web origins, and OAuth clients.
|
||||
* Deployable as a WAR, appliance, or an Openshift cloud service (SaaS).
|
||||
* Supports JBoss AS7, EAP 6.x, Wildfly, Tomcat, and Jetty applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java applications.
|
||||
* Supports JBoss AS7, EAP 6.x, and Wildfly applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java applications.
|
||||
* Javascript/HTML 5 adapter for pure Javascript apps
|
||||
* Session management from admin console
|
||||
* Revocation policies
|
||||
* Password policies
|
||||
* OpenID Connect Support
|
||||
* SAML Support
|
||||
* Token claim and SAML assertion mappings, role name mappings, etc. Ability to configure exactly what information you want in your tokens and SAML documents
|
||||
* IDP brokering or chaining. You can set up Keycloak to be a child IDP to another SAML or OIDC IDP.
|
||||
* Kerberos bridging. Logged in Kerberos users can access Keycloak SAML or OIDC applications via our Kerberos bridge.
|
||||
|
||||
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
|
||||
and video tutorials.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
-57
@@ -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
|
||||
@@ -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>
|
||||
-233
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
-56
@@ -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();
|
||||
}
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
||||
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
|
||||
@@ -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>
|
||||
@@ -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
@@ -1 +0,0 @@
|
||||
org.keycloak.broker.saml.SAMLIdentityProviderFactory
|
||||
@@ -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>
|
||||
-86
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-202
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-31
@@ -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);
|
||||
}
|
||||
-25
@@ -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
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
|
||||
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.file.FileConnectionSpi
|
||||
@@ -1,33 +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-infinispan</artifactId>
|
||||
<name>Keycloak Connections Infinispan</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.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager) {
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Cache<K, V> getCache(String name) {
|
||||
return cacheManager.getCache(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultInfinispanConnectionProviderFactory implements InfinispanConnectionProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
private boolean containerManaged;
|
||||
|
||||
@Override
|
||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||
lazyInit();
|
||||
|
||||
return new DefaultInfinispanConnectionProvider(cacheManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cacheManager != null && !containerManaged) {
|
||||
cacheManager.stop();
|
||||
}
|
||||
cacheManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
if (cacheManager == null) {
|
||||
synchronized (this) {
|
||||
if (cacheManager == null) {
|
||||
String cacheContainer = config.get("cacheContainer");
|
||||
if (cacheContainer != null) {
|
||||
initContainerManaged(cacheContainer);
|
||||
} else {
|
||||
initEmbedded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initContainerManaged(String cacheContainerLookup) {
|
||||
try {
|
||||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
containerManaged = true;
|
||||
|
||||
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve cache container", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initEmbedded() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
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;
|
||||
|
||||
logger.debug("Started embedded Infinispan cache container");
|
||||
|
||||
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
}
|
||||
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, invalidationCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
String sessionsMode = config.get("sessionsMode", "distributed");
|
||||
if (sessionsMode.equalsIgnoreCase("replicated")) {
|
||||
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||
} else if (sessionsMode.equalsIgnoreCase("distributed")) {
|
||||
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for sessionsMode");
|
||||
}
|
||||
|
||||
sessionConfigBuilder.clustering().hash()
|
||||
.numOwners(config.getInt("sessionsOwners", 2))
|
||||
.numSegments(config.getInt("sessionsSegments", 60)).build();
|
||||
}
|
||||
|
||||
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface InfinispanConnectionProvider extends Provider {
|
||||
|
||||
static final String REALM_CACHE_NAME = "realms";
|
||||
static final String USER_CACHE_NAME = "users";
|
||||
static final String SESSION_CACHE_NAME = "sessions";
|
||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||
|
||||
<K, V> Cache<K, V> getCache(String name);
|
||||
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface InfinispanConnectionProviderFactory extends ProviderFactory<InfinispanConnectionProvider> {
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanConnectionSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsInfinispan";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return InfinispanConnectionProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return InfinispanConnectionProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
||||
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.infinispan.InfinispanConnectionSpi
|
||||
@@ -1,78 +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-jpa-liquibase</artifactId>
|
||||
<name>Keycloak Connections JPA Liquibase Updater</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<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>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
|
||||
|
||||
<url>${url}</url>
|
||||
<driver>${driver}</driver>
|
||||
<username>${username}</username>
|
||||
<password>${password}</password>
|
||||
|
||||
<referenceUrl>${referenceUrl}</referenceUrl>
|
||||
<referenceDriver>${driver}</referenceDriver>
|
||||
<referenceUsername>${username}</referenceUsername>
|
||||
<referencePassword>${password}</referencePassword>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<username>sa</username>
|
||||
<password></password>
|
||||
<driver>org.h2.Driver</driver>
|
||||
</properties>
|
||||
</project>
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.changelog.ChangeSet;
|
||||
import liquibase.changelog.DatabaseChangeLog;
|
||||
import liquibase.changelog.RanChangeSet;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.DatabaseFactory;
|
||||
import liquibase.database.core.DB2Database;
|
||||
import liquibase.database.core.DerbyDatabase;
|
||||
import liquibase.database.core.FirebirdDatabase;
|
||||
import liquibase.database.core.H2Database;
|
||||
import liquibase.database.core.HsqlDatabase;
|
||||
import liquibase.database.core.InformixDatabase;
|
||||
import liquibase.database.core.MSSQLDatabase;
|
||||
import liquibase.database.core.MySQLDatabase;
|
||||
import liquibase.database.core.OracleDatabase;
|
||||
import liquibase.database.core.PostgresDatabase;
|
||||
import liquibase.database.core.SQLiteDatabase;
|
||||
import liquibase.database.core.SybaseASADatabase;
|
||||
import liquibase.database.core.SybaseDatabase;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.logging.LogFactory;
|
||||
import liquibase.logging.LogLevel;
|
||||
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;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(LiquibaseJpaUpdaterProvider.class);
|
||||
|
||||
private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
|
||||
|
||||
@Override
|
||||
public String getCurrentVersionSql() {
|
||||
return "SELECT ID from DATABASECHANGELOG ORDER BY DATEEXECUTED DESC LIMIT 1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session, 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);
|
||||
|
||||
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||
if (!changeSets.isEmpty()) {
|
||||
if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
|
||||
Statement statement = connection.createStatement();
|
||||
try {
|
||||
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);
|
||||
} catch (SQLException e) {
|
||||
logger.info("Initializing database schema");
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||
logger.debugv("Updating database from {0} to {1}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
|
||||
} else {
|
||||
logger.infov("Updating database");
|
||||
}
|
||||
}
|
||||
|
||||
liquibase.update((Contexts) null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to update database", e);
|
||||
} finally {
|
||||
ThreadLocalSessionContext.removeCurrentSession();
|
||||
}
|
||||
|
||||
logger.debug("Completed database update");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Connection connection) {
|
||||
try {
|
||||
Liquibase liquibase = getLiquibase(connection);
|
||||
|
||||
liquibase.validate();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to validate database", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Liquibase getLiquibase(Connection connection) throws Exception {
|
||||
ServiceLocator sl = ServiceLocator.getInstance();
|
||||
|
||||
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
|
||||
if (sl.getPackages().remove("liquibase.core")) {
|
||||
sl.addPackageToScan("liquibase.core.xml");
|
||||
}
|
||||
|
||||
if (sl.getPackages().remove("liquibase.parser")) {
|
||||
sl.addPackageToScan("liquibase.parser.core.xml");
|
||||
}
|
||||
|
||||
if (sl.getPackages().remove("liquibase.serializer")) {
|
||||
sl.addPackageToScan("liquibase.serializer.core.xml");
|
||||
}
|
||||
|
||||
sl.getPackages().remove("liquibase.ext");
|
||||
sl.getPackages().remove("liquibase.sdk");
|
||||
}
|
||||
|
||||
LogFactory.setInstance(new LogWrapper());
|
||||
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
||||
return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private static class LogWrapper extends LogFactory {
|
||||
|
||||
private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(LogLevel level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String logLevel, String logFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.error(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.warn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.warn(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.debug(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.debug(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.trace(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogLevel getLogLevel() {
|
||||
if (LiquibaseJpaUpdaterProvider.logger.isTraceEnabled()) {
|
||||
return LogLevel.DEBUG;
|
||||
} else if (LiquibaseJpaUpdaterProvider.logger.isDebugEnabled()) {
|
||||
return LogLevel.INFO;
|
||||
} else {
|
||||
return LogLevel.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.trace(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeSet(ChangeSet changeSet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog(String name) {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
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>
|
||||
*/
|
||||
public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFactory {
|
||||
|
||||
@Override
|
||||
public JpaUpdaterProvider create(KeycloakSession session) {
|
||||
return new LiquibaseJpaUpdaterProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "liquibase";
|
||||
}
|
||||
|
||||
}
|
||||
-23
@@ -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();
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase.custom;
|
||||
|
||||
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.statement.core.UpdateStatement;
|
||||
import liquibase.structure.core.Schema;
|
||||
import liquibase.structure.core.Table;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AddRealmCodeSecret implements CustomSqlChange {
|
||||
|
||||
private String confirmationMessage;
|
||||
|
||||
@Override
|
||||
public SqlStatement[] generateStatements(Database database) throws CustomChangeException {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Generated codeSecret for realms: ");
|
||||
|
||||
Connection connection = ((JdbcConnection) (database.getConnection())).getWrappedConnection();
|
||||
ArrayList<SqlStatement> statements = new ArrayList<SqlStatement>();
|
||||
|
||||
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 WHERE CODE_SECRET IS NULL");
|
||||
while (resultSet.next()) {
|
||||
String id = resultSet.getString(1);
|
||||
|
||||
UpdateStatement statement = new UpdateStatement(null, null, correctedTableName)
|
||||
.addNewColumnValue("CODE_SECRET", KeycloakModelUtils.generateCodeSecret())
|
||||
.setWhereClause("ID='" + id + "'");
|
||||
statements.add(statement);
|
||||
|
||||
if (!resultSet.isFirst()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(id);
|
||||
}
|
||||
|
||||
if (!statements.isEmpty()) {
|
||||
confirmationMessage = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return statements.toArray(new SqlStatement[statements.size()]);
|
||||
} catch (Exception e) {
|
||||
throw new CustomChangeException("Failed to add realm code secret", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfirmationMessage() {
|
||||
return confirmationMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileOpener(ResourceAccessor resourceAccessor) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationErrors validate(Database database) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
-97
@@ -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();
|
||||
}
|
||||
-338
@@ -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,420 +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.2.xsd">
|
||||
<changeSet author="sthorger@redhat.com" id="1.0.0.Final">
|
||||
<createTable tableName="APPLICATION_DEFAULT_ROLES">
|
||||
<column name="APPLICATION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT">
|
||||
<column name="DTYPE" type="VARCHAR(31)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ALLOWED_CLAIMS_MASK" type="BIGINT"/>
|
||||
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FULL_SCOPE_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="NOT_BEFORE" type="INT"/>
|
||||
<column name="PUBLIC_CLIENT" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SECRET" type="VARCHAR(255)"/>
|
||||
<column name="BASE_URL" type="VARCHAR(255)"/>
|
||||
<column name="BEARER_ONLY" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="MANAGEMENT_URL" type="VARCHAR(255)"/>
|
||||
<column name="SURROGATE_AUTH_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DIRECT_GRANTS_ONLY" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_SESSION">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ACTION" type="INT"/>
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
<column name="REDIRECT_URI" type="VARCHAR(255)"/>
|
||||
<column name="STATE" type="VARCHAR(255)"/>
|
||||
<column name="TIMESTAMP" type="INT"/>
|
||||
<column name="SESSION_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_SESSION_ROLE">
|
||||
<column name="ROLE_ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CLIENT_SESSION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="COMPOSITE_ROLE">
|
||||
<column name="COMPOSITE" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CHILD_ROLE" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CREDENTIAL">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DEVICE" type="VARCHAR(255)"/>
|
||||
<column name="HASH_ITERATIONS" type="INT"/>
|
||||
<column name="SALT" type="BLOB(16)"/>
|
||||
<column name="TYPE" type="VARCHAR(255)"/>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="EVENT_ENTITY">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CLIENT_ID" type="VARCHAR(255)"/>
|
||||
<column name="DETAILS_JSON" type="VARCHAR(2550)"/>
|
||||
<column name="ERROR" type="VARCHAR(255)"/>
|
||||
<column name="IP_ADDRESS" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="SESSION_ID" type="VARCHAR(255)"/>
|
||||
<column name="TIME" type="BIGINT"/>
|
||||
<column name="TYPE" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="FED_PROVIDERS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USERFEDERATIONPROVIDERS_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="KEYCLOAK_ROLE">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="APP_REALM_CONSTRAINT" type="VARCHAR(36)"/>
|
||||
<column name="APPLICATION_ROLE" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DESCRIPTION" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="APPLICATION" type="VARCHAR(36)"/>
|
||||
<column name="REALM" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ACCESS_CODE_LIFESPAN" type="INT"/>
|
||||
<column name="USER_ACTION_LIFESPAN" type="INT"/>
|
||||
<column name="ACCESS_TOKEN_LIFESPAN" type="INT"/>
|
||||
<column name="ACCOUNT_THEME" type="VARCHAR(255)"/>
|
||||
<column name="ADMIN_THEME" type="VARCHAR(255)"/>
|
||||
<column name="EMAIL_THEME" type="VARCHAR(255)"/>
|
||||
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="EVENTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="EVENTS_EXPIRATION" type="BIGINT"/>
|
||||
<column name="LOGIN_THEME" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="NOT_BEFORE" type="INT"/>
|
||||
<column name="PASSWORD_CRED_GRANT_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="PASSWORD_POLICY" type="VARCHAR(255)"/>
|
||||
<column name="PRIVATE_KEY" type="VARCHAR(2048)"/>
|
||||
<column name="PUBLIC_KEY" type="VARCHAR(2048)"/>
|
||||
<column name="REGISTRATION_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REMEMBER_ME" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="RESET_PASSWORD_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SOCIAL" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SSL_REQUIRED" type="VARCHAR(255)"/>
|
||||
<column name="SSO_IDLE_TIMEOUT" type="INT"/>
|
||||
<column name="SSO_MAX_LIFESPAN" type="INT"/>
|
||||
<column name="UPDATE_PROFILE_ON_SOC_LOGIN" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VERIFY_EMAIL" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="MASTER_ADMIN_APP" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_APPLICATION">
|
||||
<column name="APPLICATION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_ATTRIBUTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_DEFAULT_ROLES">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_EVENTS_LISTENERS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_REQUIRED_CREDENTIAL">
|
||||
<column name="TYPE" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FORM_LABEL" type="VARCHAR(255)"/>
|
||||
<column name="INPUT" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SECRET" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_SMTP_CONFIG">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_SOCIAL_CONFIG">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REDIRECT_URIS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="SCOPE_MAPPING">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USERNAME_LOGIN_FAILURE">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USERNAME" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FAILED_LOGIN_NOT_BEFORE" type="INT"/>
|
||||
<column name="LAST_FAILURE" type="BIGINT"/>
|
||||
<column name="LAST_IP_FAILURE" type="VARCHAR(255)"/>
|
||||
<column name="NUM_FAILURES" type="INT"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_ATTRIBUTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_ENTITY">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="EMAIL" type="VARCHAR(255)"/>
|
||||
<column name="EMAIL_CONSTRAINT" type="VARCHAR(255)"/>
|
||||
<column name="EMAIL_VERIFIED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FEDERATION_LINK" type="VARCHAR(255)"/>
|
||||
<column name="FIRST_NAME" type="VARCHAR(255)"/>
|
||||
<column name="LAST_NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="TOTP" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USERNAME" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_FEDERATION_CONFIG">
|
||||
<column name="USER_FEDERATION_PROVIDER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_FEDERATION_PROVIDER">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CHANGED_SYNC_PERIOD" type="INT"/>
|
||||
<column name="DISPLAY_NAME" type="VARCHAR(255)"/>
|
||||
<column name="FULL_SYNC_PERIOD" type="INT"/>
|
||||
<column name="LAST_SYNC" type="INT"/>
|
||||
<column name="PRIORITY" type="INT"/>
|
||||
<column name="PROVIDER_NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_REQUIRED_ACTION">
|
||||
<column name="ACTION" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_ROLE_MAPPING">
|
||||
<column name="ROLE_ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_SESSION">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="AUTH_METHOD" type="VARCHAR(255)"/>
|
||||
<column name="IP_ADDRESS" type="VARCHAR(255)"/>
|
||||
<column name="LAST_SESSION_REFRESH" type="INT"/>
|
||||
<column name="LOGIN_USERNAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="REMEMBER_ME" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STARTED" type="INT"/>
|
||||
<column name="USER_ID" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_SOCIAL_LINK">
|
||||
<column name="SOCIAL_PROVIDER" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="SOCIAL_USER_ID" type="VARCHAR(255)"/>
|
||||
<column name="SOCIAL_USERNAME" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="WEB_ORIGINS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="REALM_ID, NAME" constraintName="CONSTRAINT_1" tableName="REALM_SOCIAL_CONFIG"/>
|
||||
<addPrimaryKey columnNames="REALM_ID, USERNAME" constraintName="CONSTRAINT_17" tableName="USERNAME_LOGIN_FAILURE"/>
|
||||
<addPrimaryKey columnNames="ACTION, USER_ID" constraintName="CONSTRAINT_2" tableName="USER_REQUIRED_ACTION"/>
|
||||
<addPrimaryKey columnNames="SOCIAL_PROVIDER, USER_ID" constraintName="CONSTRAINT_3" tableName="USER_SOCIAL_LINK"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_4" tableName="EVENT_ENTITY"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_4A" tableName="REALM"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, ROLE_ID" constraintName="CONSTRAINT_5" tableName="CLIENT_SESSION_ROLE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_57" tableName="USER_SESSION"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_5C" tableName="USER_FEDERATION_PROVIDER"/>
|
||||
<addPrimaryKey columnNames="NAME, USER_ID" constraintName="CONSTRAINT_6" tableName="USER_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_7" tableName="CLIENT"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_8" tableName="CLIENT_SESSION"/>
|
||||
<addPrimaryKey columnNames="CLIENT_ID, ROLE_ID" constraintName="CONSTRAINT_81" tableName="SCOPE_MAPPING"/>
|
||||
<addPrimaryKey columnNames="NAME, REALM_ID" constraintName="CONSTRAINT_9" tableName="REALM_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="REALM_ID, TYPE" constraintName="CONSTRAINT_92" tableName="REALM_REQUIRED_CREDENTIAL"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_A" tableName="KEYCLOAK_ROLE"/>
|
||||
<addPrimaryKey columnNames="ROLE_ID, USER_ID" constraintName="CONSTRAINT_C" tableName="USER_ROLE_MAPPING"/>
|
||||
<addPrimaryKey columnNames="REALM_ID, NAME" constraintName="CONSTRAINT_E" tableName="REALM_SMTP_CONFIG"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_F" tableName="CREDENTIAL"/>
|
||||
<addPrimaryKey columnNames="USER_FEDERATION_PROVIDER_ID, NAME" constraintName="CONSTRAINT_F9" tableName="USER_FEDERATION_CONFIG"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_FB" tableName="USER_ENTITY"/>
|
||||
<addUniqueConstraint columnNames="ROLE_ID" constraintName="UK_8AELWNIBJI49AVXSRTUF6XJOW" tableName="APPLICATION_DEFAULT_ROLES"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,NAME" constraintName="UK_B71CJLBENV945RB6GCON438AT" tableName="CLIENT"/>
|
||||
<addUniqueConstraint columnNames="USERFEDERATIONPROVIDERS_ID" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" tableName="FED_PROVIDERS"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,EMAIL_CONSTRAINT" constraintName="UK_DYKN684SL8UP1CRFEI6ECKHD7" tableName="USER_ENTITY"/>
|
||||
<addUniqueConstraint columnNames="ROLE_ID" constraintName="UK_H4WPD7W4HSOOLNI3H0SW7BTJE" tableName="REALM_DEFAULT_ROLES"/>
|
||||
<addUniqueConstraint columnNames="NAME,APP_REALM_CONSTRAINT" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2" tableName="KEYCLOAK_ROLE"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID" constraintName="UK_L5QGA3RFME47335JY8JXYXH3I" tableName="REALM_APPLICATION"/>
|
||||
<addUniqueConstraint columnNames="NAME" constraintName="UK_ORVSDMLA56612EAEFIQ6WL5OI" tableName="REALM"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,USERNAME" constraintName="UK_RU8TT6T700S9V50BU18WS5HA6" tableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_ROLE" constraintName="FK_11B7SGQW18I532811V7O2DV76" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="REDIRECT_URIS" constraintName="FK_1BURS8PB4OUJ97H5WUPPAHV9F" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="USER_FEDERATION_PROVIDER" constraintName="FK_1FJ32F6PTOLW2QY60CD8N01E8" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="FED_PROVIDERS" constraintName="FK_213LYQ09FKXQ8K8NY8DY3737T" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_REQUIRED_CREDENTIAL" constraintName="FK_5HG65LYBEVAVKQFKI3KPONH9V" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_ATTRIBUTE" constraintName="FK_5HRM2VLF9QL5FU043KQEPOVBR" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_SOCIAL_LINK" constraintName="FK_68CJYS5UWM55UY823Y75XG4OM" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_REQUIRED_ACTION" constraintName="FK_6QJ3W1JW9CVAFHE19BWSIUVMD" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM" baseTableName="KEYCLOAK_ROLE" constraintName="FK_6VYQFE4CN4WLQ8R6KT5VDSJ5C" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SMTP_CONFIG" constraintName="FK_70EJ8XDXGXD0B9HH6180IRR0O" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="REALM_APPLICATION" constraintName="FK_71S3P0DIUXAWWQQSA528UBY2Q" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="APPLICATION_DEFAULT_ROLES" constraintName="FK_8AELWNIBJI49AVXSRTUF6XJOW" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_ATTRIBUTE" constraintName="FK_8SHXD6L3E9ATQUKACXGPFFPTW" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="COMPOSITE" baseTableName="COMPOSITE_ROLE" constraintName="FK_A63WVEKFTU8JO1PNJ81E7MCE2" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="SESSION_ID" baseTableName="CLIENT_SESSION" constraintName="FK_B4AO2VCVAT6UKAU74WBWTFQO1" referencedColumnNames="ID" referencedTableName="USER_SESSION"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_ROLE_MAPPING" constraintName="FK_C4FQV34P1MBYLLOXANG7B1Q3L" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USERFEDERATIONPROVIDERS_ID" baseTableName="FED_PROVIDERS" constraintName="FK_DCCIRJLIPU1478VQC89DID88C" referencedColumnNames="ID" referencedTableName="USER_FEDERATION_PROVIDER"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_ROLES" constraintName="FK_EVUDB1PPW84OXFAX2DRS03ICC" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CHILD_ROLE" baseTableName="COMPOSITE_ROLE" constraintName="FK_GR7THLLB9LU8Q4VQA4524JJY8" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="REALM_DEFAULT_ROLES" constraintName="FK_H4WPD7W4HSOOLNI3H0SW7BTJE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_EVENTS_LISTENERS" constraintName="FK_H846O4H0W8EPX5NXEV9F5Y69J" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_APPLICATION" constraintName="FK_L5QGA3RFME47335JY8JXYXH3I" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="WEB_ORIGINS" constraintName="FK_LOJPHO213XCX4WNKOG82SSRFY" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="APPLICATION_DEFAULT_ROLES" constraintName="FK_MAYLTS7KLWQW2H8M2B5JOYTKY" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="SCOPE_MAPPING" constraintName="FK_OUSE064PLMLR732LXJCN1Q5F1" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="SCOPE_MAPPING" constraintName="FK_P3RH9GRKU11KQFRS4FLTT7RNQ" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT" constraintName="FK_P56CTINXXB9GSK57FO49F9TAC" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="CREDENTIAL" constraintName="FK_PFYR0GLASQYL0DEI3KL69R6V0" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION" baseTableName="KEYCLOAK_ROLE" constraintName="FK_PIMO5LE2C0RAL09FL8CM9WFW9" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="MASTER_ADMIN_APP" baseTableName="REALM" constraintName="FK_RSAF444KK6QRKMS7N56AIWQ5Y" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SOCIAL_CONFIG" constraintName="FK_SV5I3C2TI7G0G922FGE683SOV" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_FEDERATION_PROVIDER_ID" baseTableName="USER_FEDERATION_CONFIG" constraintName="FK_T13HPU1J94R2EBPEKR39X5EU5" referencedColumnNames="ID" referencedTableName="USER_FEDERATION_PROVIDER"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -1,59 +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.2.xsd">
|
||||
<changeSet author="sthorger@redhat.com" id="1.1.0.Beta1">
|
||||
<delete tableName="CLIENT_SESSION_ROLE"/>
|
||||
<delete tableName="CLIENT_SESSION"/>
|
||||
<delete tableName="USER_SESSION"/>
|
||||
|
||||
<createTable tableName="CLIENT_ATTRIBUTES">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(2048)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_SESSION_NOTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="CLIENT_SESSION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="APP_NODE_REGISTRATIONS">
|
||||
<column name="APPLICATION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="INT"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="AUTH_METHOD" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="PROTOCOL" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="REALM">
|
||||
<column name="CERTIFICATE" type="VARCHAR(2048)"/>
|
||||
<column name="CODE_SECRET" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="NODE_REREG_TIMEOUT" type="INT" defaultValueNumeric="0"/>
|
||||
</addColumn>
|
||||
<addPrimaryKey columnNames="CLIENT_ID, NAME" constraintName="CONSTRAINT_3C" tableName="CLIENT_ATTRIBUTES"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_5E" tableName="CLIENT_SESSION_NOTE"/>
|
||||
<addPrimaryKey columnNames="APPLICATION_ID, NAME" constraintName="CONSTRAINT_84" tableName="APP_NODE_REGISTRATIONS"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_ATTRIBUTES" constraintName="FK3C47C64BEACCA966" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_NOTE" constraintName="FK5EDFB00FF51C2736" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="APP_NODE_REGISTRATIONS" constraintName="FK8454723BA992F594" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.AddRealmCodeSecret"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -1,6 +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.2.xsd">
|
||||
<changeSet author="sthorger@redhat.com" id="1.1.0.Final">
|
||||
<renameColumn tableName="EVENT_ENTITY" oldColumnName="TIME" newColumnName="EVENT_TIME" columnDataType="BIGINT"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -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>
|
||||
@@ -1,7 +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.2.xsd">
|
||||
<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 +0,0 @@
|
||||
org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<version>1.0.4.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+4
-91
@@ -1,21 +1,12 @@
|
||||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
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;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,15 +15,13 @@ import java.util.Map;
|
||||
*/
|
||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
|
||||
|
||||
private volatile EntityManagerFactory emf;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public JpaConnectionProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
lazyInit();
|
||||
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em = PersistenceExceptionConverter.create(em);
|
||||
@@ -57,22 +46,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
private void lazyInit() {
|
||||
if (emf == null) {
|
||||
synchronized (this) {
|
||||
if (emf == null) {
|
||||
logger.debug("Initializing JPA connections");
|
||||
|
||||
Connection connection = null;
|
||||
|
||||
String unitName = config.get("unitName");
|
||||
String databaseSchema = config.get("databaseSchema");
|
||||
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
|
||||
// Only load config from keycloak-server.json if unitName is not specified
|
||||
@@ -105,84 +83,19 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||
properties.put("hibernate.dialect", driverDialect);
|
||||
}
|
||||
|
||||
String databaseSchema = config.get("databaseSchema", "validate");
|
||||
if (databaseSchema != null) {
|
||||
if (databaseSchema.equals("development-update")) {
|
||||
properties.put("hibernate.hbm2ddl.auto", "update");
|
||||
databaseSchema = null;
|
||||
} else if (databaseSchema.equals("development-validate")) {
|
||||
properties.put("hibernate.hbm2ddl.auto", "validate");
|
||||
databaseSchema = null;
|
||||
}
|
||||
properties.put("hibernate.hbm2ddl.auto", databaseSchema);
|
||||
}
|
||||
|
||||
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
||||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||
}
|
||||
|
||||
if (databaseSchema != null) {
|
||||
logger.trace("Updating database");
|
||||
|
||||
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
|
||||
if (updater == null) {
|
||||
throw new RuntimeException("Can't update database: JPA updater provider not found");
|
||||
}
|
||||
|
||||
connection = getConnection();
|
||||
|
||||
if (databaseSchema.equals("update")) {
|
||||
String currentVersion = null;
|
||||
try {
|
||||
ResultSet resultSet = connection.createStatement().executeQuery(updater.getCurrentVersionSql());
|
||||
if (resultSet.next()) {
|
||||
currentVersion = resultSet.getString(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
|
||||
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
|
||||
updater.update(session, connection);
|
||||
} else {
|
||||
logger.debug("Database is up to date");
|
||||
}
|
||||
} else if (databaseSchema.equals("validate")) {
|
||||
updater.validate(connection);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||
}
|
||||
|
||||
logger.trace("Database update completed");
|
||||
}
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
emf = Persistence.createEntityManagerFactory(unitName, properties);
|
||||
logger.trace("EntityManagerFactory created");
|
||||
|
||||
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
logger.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() {
|
||||
try {
|
||||
String dataSourceLookup = config.get("dataSource");
|
||||
if (dataSourceLookup != null) {
|
||||
DataSource dataSource = (DataSource) new InitialContext().lookup(dataSourceLookup);
|
||||
return dataSource.getConnection();
|
||||
} else {
|
||||
Class.forName(config.get("driver"));
|
||||
return DriverManager.getConnection(config.get("url"), config.get("user"), config.get("password"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to connect to database", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package org.keycloak.connections.jpa.updater;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaUpdaterProvider extends Provider {
|
||||
|
||||
public String FIRST_VERSION = "1.0.0.Final";
|
||||
|
||||
public String LAST_VERSION = "1.2.0.Beta1";
|
||||
|
||||
public String getCurrentVersionSql();
|
||||
|
||||
public void update(KeycloakSession session, Connection connection);
|
||||
|
||||
public void validate(Connection connection);
|
||||
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package org.keycloak.connections.jpa.updater;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaUpdaterProviderFactory extends ProviderFactory<JpaUpdaterProvider> {
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.keycloak.connections.jpa.updater;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JpaUpdaterSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsJpaUpdater";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return JpaUpdaterProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return JpaUpdaterProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,21 +11,16 @@
|
||||
<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 +28,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>
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
org.keycloak.connections.jpa.JpaConnectionSpi
|
||||
org.keycloak.connections.jpa.updater.JpaUpdaterSpi
|
||||
org.keycloak.connections.jpa.JpaConnectionSpi
|
||||
@@ -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>
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater.impl;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
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 java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
||||
|
||||
public static final Logger log = Logger.getLogger(DefaultMongoUpdaterProvider.class);
|
||||
|
||||
public static final String CHANGE_LOG_COLLECTION = "databaseChangeLog";
|
||||
|
||||
private Class<? extends Update>[] updates = new Class[]{
|
||||
Update1_0_0_Final.class,
|
||||
Update1_1_0_Beta1.class,
|
||||
Update1_2_0_Beta1.class
|
||||
};
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session, DB db) {
|
||||
log.debug("Starting database update");
|
||||
try {
|
||||
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
|
||||
boolean realmExists = db.collectionExists("realms");
|
||||
|
||||
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
|
||||
|
||||
List<String> executed = new LinkedList<String>();
|
||||
if (!changeLogExists && realmExists) {
|
||||
Update1_0_0_Final u = new Update1_0_0_Final();
|
||||
executed.add(u.getId());
|
||||
createLog(changeLog, u, 1);
|
||||
} else if (changeLogExists) {
|
||||
DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
|
||||
while (cursor.hasNext()) {
|
||||
executed.add((String) cursor.next().get("_id"));
|
||||
}
|
||||
}
|
||||
|
||||
List<Update> updatesToRun = new LinkedList<Update>();
|
||||
for (Class<? extends Update> updateClass : updates) {
|
||||
Update u = updateClass.newInstance();
|
||||
if (!executed.contains(u.getId())) {
|
||||
updatesToRun.add(u);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updatesToRun.isEmpty()) {
|
||||
if (executed.isEmpty()) {
|
||||
log.info("Initializing database schema");
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.infov("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
|
||||
} else {
|
||||
log.debugv("Updating database");
|
||||
}
|
||||
}
|
||||
|
||||
int order = executed.size();
|
||||
for (Update u : updatesToRun) {
|
||||
log.debugv("Executing updates for {0}", u.getId());
|
||||
|
||||
u.setLog(log);
|
||||
u.setDb(db);
|
||||
u.update(session);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
|
||||
changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater.impl;
|
||||
|
||||
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>
|
||||
*/
|
||||
public class DefaultMongoUpdaterProviderFactory implements MongoUpdaterProviderFactory {
|
||||
|
||||
@Override
|
||||
public MongoUpdaterProvider create(KeycloakSession session) {
|
||||
return new DefaultMongoUpdaterProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater.impl.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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class Update {
|
||||
|
||||
protected DB db;
|
||||
|
||||
protected Logger log;
|
||||
|
||||
public abstract String getId();
|
||||
|
||||
public abstract void update(KeycloakSession session) throws ClassNotFoundException;
|
||||
|
||||
protected DBCollection createCollection(String name) {
|
||||
if (db.collectionExists(name)) {
|
||||
throw new RuntimeException("Failed to create collection {0}: collection already exists");
|
||||
}
|
||||
|
||||
DBCollection col = db.getCollection(name);
|
||||
log.debugv("Created collection {0}", name);
|
||||
return col;
|
||||
}
|
||||
|
||||
protected void ensureIndex(String name, String field, boolean unique, boolean sparse) {
|
||||
ensureIndex(name, new String[]{field}, unique, sparse);
|
||||
}
|
||||
|
||||
protected void ensureIndex(String name, String[] fields, boolean unique, boolean sparse) {
|
||||
DBCollection col = db.getCollection(name);
|
||||
|
||||
BasicDBObject o = new BasicDBObject();
|
||||
for (String f : fields) {
|
||||
o.append(f, 1);
|
||||
}
|
||||
|
||||
col.ensureIndex(o, new BasicDBObject("unique", unique).append("sparse", sparse));
|
||||
log.debugv("Created index {0}, fields={1}, unique={2}, sparse={3}", name, Arrays.toString(fields), unique, sparse);
|
||||
}
|
||||
|
||||
protected void deleteEntries(String collection) {
|
||||
db.getCollection(collection).remove(new BasicDBObject());
|
||||
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;
|
||||
}
|
||||
|
||||
public void setDb(DB db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater.impl.updates;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import org.keycloak.connections.mongo.updater.impl.DefaultMongoUpdaterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Update1_0_0_Final extends Update {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "1.0.0.Final";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session) throws ClassNotFoundException {
|
||||
DBCollection realmsCollection = db.getCollection("realms");
|
||||
realmsCollection.ensureIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
|
||||
|
||||
DefaultMongoUpdaterProvider.log.debugv("Created collection {0}", "realms");
|
||||
|
||||
createCollection("users");
|
||||
ensureIndex("users", new String[] { "realmId", "username"}, true, false);
|
||||
ensureIndex("users", "emailIndex", true, true);
|
||||
|
||||
createCollection("roles");
|
||||
ensureIndex("roles", "nameIndex", true, false);
|
||||
|
||||
createCollection("applications");
|
||||
ensureIndex("applications", new String[]{"realmId", "name"}, true, false);
|
||||
|
||||
createCollection("oauthClients");
|
||||
ensureIndex("oauthClients", new String[] { "realmId", "name"}, true, false);
|
||||
|
||||
createCollection("userFailures");
|
||||
|
||||
createCollection("sessions");
|
||||
|
||||
createCollection("clientSessions");
|
||||
}
|
||||
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater.impl.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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Update1_1_0_Beta1 extends Update {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "1.1.0.Beta1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session) {
|
||||
deleteEntries("clientSessions");
|
||||
deleteEntries("sessions");
|
||||
|
||||
addRealmCodeSecret();
|
||||
}
|
||||
|
||||
private void addRealmCodeSecret() {
|
||||
DBCollection realms = db.getCollection("realms");
|
||||
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("codeSecret").is(null).get();
|
||||
|
||||
DBCursor objects = realms.find(query);
|
||||
while (objects.hasNext()) {
|
||||
DBObject object = objects.next();
|
||||
object.put("codeSecret", KeycloakModelUtils.generateCodeSecret());
|
||||
realms.save(object);
|
||||
|
||||
log.debugv("Added realm.codeSecret, id={0}", object.get("id"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-269
@@ -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
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.mongo.updater.impl.DefaultMongoUpdaterProviderFactory
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<version>1.0.4.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
Executable → Regular
+9
-82
@@ -2,7 +2,6 @@ package org.keycloak.connections.mongo;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -10,12 +9,8 @@ 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.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
@@ -28,18 +23,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 +44,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 +56,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) {
|
||||
@@ -78,38 +64,22 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
String host = config.get("host", ServerAddress.defaultHost());
|
||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||
String dbName = config.get("db", "keycloak");
|
||||
boolean clearOnStartup = config.getBoolean("clearOnStartup", false);
|
||||
|
||||
String user = config.get("user");
|
||||
String password = config.get("password");
|
||||
|
||||
MongoClientOptions clientOptions = getClientOptions();
|
||||
if (user != null && password != null) {
|
||||
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
||||
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
|
||||
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential));
|
||||
} else {
|
||||
client = new MongoClient(new ServerAddress(host, port), clientOptions);
|
||||
client = new MongoClient(host, port);
|
||||
}
|
||||
|
||||
this.db = client.getDB(dbName);
|
||||
|
||||
String databaseSchema = config.get("databaseSchema");
|
||||
if (databaseSchema != null) {
|
||||
if (databaseSchema.equals("update")) {
|
||||
MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
|
||||
this.mongoStore = new MongoStoreImpl(db, clearOnStartup, getManagedEntities());
|
||||
|
||||
if (mongoUpdater == null) {
|
||||
throw new RuntimeException("Can't update database: Mongo updater provider not found");
|
||||
}
|
||||
|
||||
mongoUpdater.update(session, db);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||
}
|
||||
}
|
||||
|
||||
this.mongoStore = new MongoStoreImpl(db, getManagedEntities());
|
||||
|
||||
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
||||
logger.infof("Initialized mongo model. host: %s, port: %d, db: %s, clearOnStartup: %b", host, port, dbName, clearOnStartup);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -138,47 +108,4 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
return "default";
|
||||
}
|
||||
|
||||
protected MongoClientOptions getClientOptions() {
|
||||
MongoClientOptions.Builder builder = MongoClientOptions.builder();
|
||||
checkIntOption("connectionsPerHost", builder);
|
||||
checkIntOption("threadsAllowedToBlockForConnectionMultiplier", builder);
|
||||
checkIntOption("maxWaitTime", builder);
|
||||
checkIntOption("connectTimeout", builder);
|
||||
checkIntOption("socketTimeout", builder);
|
||||
checkBooleanOption("socketKeepAlive", builder);
|
||||
checkBooleanOption("autoConnectRetry", builder);
|
||||
if (config.getLong("maxAutoConnectRetryTime") != null) {
|
||||
builder.maxAutoConnectRetryTime(config.getLong("maxAutoConnectRetryTime"));
|
||||
}
|
||||
if(config.getBoolean("ssl", false)) {
|
||||
builder.socketFactory(SSLSocketFactory.getDefault());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
protected void checkBooleanOption(String optionName, MongoClientOptions.Builder builder) {
|
||||
Boolean val = config.getBoolean(optionName);
|
||||
if (val != null) {
|
||||
try {
|
||||
Method m = MongoClientOptions.Builder.class.getMethod(optionName, boolean.class);
|
||||
m.invoke(builder, val);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Problem configuring boolean option " + optionName + " for mongo client. Ensure you used correct value true or false and if this option is supported by mongo driver", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkIntOption(String optionName, MongoClientOptions.Builder builder) {
|
||||
Integer val = config.getInt(optionName);
|
||||
if (val != null) {
|
||||
try {
|
||||
Method m = MongoClientOptions.Builder.class.getMethod(optionName, int.class);
|
||||
m.invoke(builder, val);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Problem configuring int option " + optionName + " for mongo client. Ensure you used correct value (number) and if this option is supported by mongo driver", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.keycloak.connections.mongo.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Target({TYPE})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface MongoIndex {
|
||||
|
||||
String[] fields();
|
||||
|
||||
boolean unique() default false;
|
||||
|
||||
boolean sparse() default false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.keycloak.connections.mongo.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Target({TYPE})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface MongoIndexes {
|
||||
|
||||
MongoIndex[] value();
|
||||
|
||||
}
|
||||
+69
-10
@@ -11,6 +11,8 @@ import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.MongoIndexes;
|
||||
import org.keycloak.connections.mongo.api.MongoStore;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.connections.mongo.api.context.MongoTask;
|
||||
@@ -18,7 +20,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 +37,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;
|
||||
|
||||
@@ -58,7 +57,7 @@ public class MongoStoreImpl implements MongoStore {
|
||||
new ConcurrentHashMap<Class<?>, EntityInfo>();
|
||||
|
||||
|
||||
public MongoStoreImpl(DB database, Class<?>[] managedEntityTypes) {
|
||||
public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<?>[] managedEntityTypes) {
|
||||
this.database = database;
|
||||
|
||||
mapperRegistry = new MapperRegistry();
|
||||
@@ -74,10 +73,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());
|
||||
@@ -91,6 +86,13 @@ public class MongoStoreImpl implements MongoStore {
|
||||
mapperRegistry.addAppObjectMapper(new MongoEntityMapper(this, mapperRegistry, type));
|
||||
mapperRegistry.addDBObjectMapper(new BasicDBObjectMapper(this, mapperRegistry, type));
|
||||
}
|
||||
|
||||
if (clearCollectionsOnStartup) {
|
||||
// dropDatabase();
|
||||
clearManagedCollections(managedEntityTypes);
|
||||
}
|
||||
|
||||
initManagedCollections(managedEntityTypes);
|
||||
}
|
||||
|
||||
protected void dropDatabase() {
|
||||
@@ -98,6 +100,63 @@ public class MongoStoreImpl implements MongoStore {
|
||||
logger.info("Database " + this.database.getName() + " dropped in MongoDB");
|
||||
}
|
||||
|
||||
// Don't drop database, but just clear all data in managed collections (useful for export/import or during development)
|
||||
protected void clearManagedCollections(Class<?>[] managedEntityTypes) {
|
||||
for (Class<?> clazz : managedEntityTypes) {
|
||||
DBCollection dbCollection = getDBCollectionForType(clazz);
|
||||
if (dbCollection != null) {
|
||||
dbCollection.remove(new BasicDBObject());
|
||||
logger.debug("Collection " + dbCollection.getName() + " cleared from " + this.database.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void initManagedCollections(Class<?>[] managedEntityTypes) {
|
||||
for (Class<?> clazz : managedEntityTypes) {
|
||||
EntityInfo entityInfo = getEntityInfo(clazz);
|
||||
String dbCollectionName = entityInfo.getDbCollectionName();
|
||||
if (dbCollectionName != null && !database.collectionExists(dbCollectionName)) {
|
||||
DBCollection dbCollection = database.getCollection(dbCollectionName);
|
||||
|
||||
logger.debug("Created collection " + dbCollection.getName() + " in " + this.database.getName());
|
||||
|
||||
MongoIndex index = clazz.getAnnotation(MongoIndex.class);
|
||||
if (index != null) {
|
||||
createIndex(dbCollection, index);
|
||||
}
|
||||
|
||||
MongoIndexes indexes = clazz.getAnnotation(MongoIndexes.class);
|
||||
if (indexes != null) {
|
||||
for (MongoIndex i : indexes.value()) {
|
||||
createIndex(dbCollection, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void createIndex(DBCollection dbCollection, MongoIndex index) {
|
||||
BasicDBObject fields = new BasicDBObject();
|
||||
for (String f : index.fields()) {
|
||||
fields.put(f, 1);
|
||||
}
|
||||
|
||||
boolean unique = index.unique();
|
||||
boolean sparse = index.sparse();
|
||||
|
||||
BasicDBObject options = new BasicDBObject();
|
||||
if (unique) {
|
||||
options.put("unique", unique);
|
||||
}
|
||||
if (sparse) {
|
||||
options.put("sparse", sparse);
|
||||
}
|
||||
|
||||
dbCollection.ensureIndex(fields, options);
|
||||
|
||||
logger.debug("Created index " + fields + "(options: " + options + ") on " + dbCollection.getName() + " in " + this.database.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) {
|
||||
Class<? extends MongoEntity> clazz = entity.getClass();
|
||||
@@ -266,7 +325,7 @@ public class MongoStoreImpl implements MongoStore {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
BasicDBObject dbQuery = new BasicDBObject("_id", id);
|
||||
dbCollection.remove(dbQuery);
|
||||
//logger.debugf("Entity of type: %s , id: %s removed from MongoDB.", type, id);
|
||||
logger.info("Entity of type: " + type + ", id: " + id + " removed from MongoDB.");
|
||||
|
||||
context.addRemovedEntity(found);
|
||||
return true;
|
||||
@@ -282,7 +341,7 @@ public class MongoStoreImpl implements MongoStore {
|
||||
} else {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
dbCollection.remove(query);
|
||||
//logger.debug("Removed %d" + foundObjects.size() + " entities of type: " + type + ", query: " + query);
|
||||
logger.info("Removed " + foundObjects.size() + " entities of type: " + type + ", query: " + query);
|
||||
|
||||
for (MongoIdentifiableEntity found : foundObjects) {
|
||||
context.addRemovedEntity(found);;
|
||||
|
||||
-1
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
-44
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
||||
import org.keycloak.connections.mongo.impl.EntityInfo;
|
||||
import org.keycloak.connections.mongo.impl.MongoStoreImpl;
|
||||
import org.keycloak.models.utils.reflection.Property;
|
||||
import org.keycloak.util.reflections.Types;
|
||||
import org.keycloak.models.utils.reflection.Types;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
+3
-8
@@ -8,7 +8,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* For now, there is support just for convert to Map<String, simpleType>
|
||||
* For now, there is support just for convert to Map<String, String>
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@@ -18,15 +18,10 @@ public class BasicDBObjectToMapMapper implements Mapper<BasicDBObject, Map> {
|
||||
public Map convertObject(MapperContext<BasicDBObject, Map> context) {
|
||||
BasicDBObject dbObjectToConvert = context.getObjectToConvert();
|
||||
|
||||
HashMap<String, Object> result = new HashMap<String, Object>();
|
||||
HashMap<String, String> result = new HashMap<String, String>();
|
||||
for (Map.Entry<String, Object> entry : dbObjectToConvert.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
// Workaround as manually inserted numbers into mongo may be treated as "Double"
|
||||
if (value instanceof Double && context.getGenericTypes().get(1) == Integer.class) {
|
||||
value = ((Double)value).intValue();
|
||||
}
|
||||
String value = (String)entry.getValue();
|
||||
|
||||
if (key.contains(MapMapper.DOT_PLACEHOLDER)) {
|
||||
key = key.replaceAll(MapMapper.DOT_PLACEHOLDER, ".");
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+4
-7
@@ -8,7 +8,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* For now, we support just convert from Map<String, simpleType>
|
||||
* For now, we support just convert from Map<String, String>
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@@ -25,16 +25,13 @@ 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();
|
||||
String value = (String)entry.getValue();
|
||||
|
||||
if (key.contains(".")) {
|
||||
key = key.replaceAll("\\.", DOT_PLACEHOLDER);
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoUpdaterProvider extends Provider {
|
||||
|
||||
public void update(KeycloakSession session, DB db);
|
||||
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoUpdaterProviderFactory extends ProviderFactory<MongoUpdaterProvider> {
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoUpdaterSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsMongoUpdater";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return MongoUpdaterProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return MongoUpdaterProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
org.keycloak.connections.mongo.MongoConnectionSpi
|
||||
org.keycloak.connections.mongo.updater.MongoUpdaterSpi
|
||||
org.keycloak.connections.mongo.MongoConnectionSpi
|
||||
+1
-5
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<version>1.0.4.Final</version>
|
||||
</parent>
|
||||
<name>Connections Parent</name>
|
||||
<description/>
|
||||
@@ -14,11 +14,7 @@
|
||||
|
||||
<modules>
|
||||
<module>jpa</module>
|
||||
<module>jpa-liquibase</module>
|
||||
<module>infinispan</module>
|
||||
<module>mongo</module>
|
||||
<module>file</module>
|
||||
<module>mongo-update</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<version>1.0.4.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -31,7 +31,7 @@ public class SkeletonKeyContextResolver implements ContextResolver<ObjectMapper>
|
||||
|
||||
@Override
|
||||
public ObjectMapper getContext(Class<?> type) {
|
||||
if (type.getPackage() != null && type.getPackage().getName().startsWith(getClass().getPackage().getName())) return mapper;
|
||||
if (type.getPackage().getName().startsWith(getClass().getPackage().getName())) return mapper;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+2
-54
@@ -4,36 +4,23 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<version>1.0.4.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<name>Keycloak Core</name>
|
||||
<packaging>jar</packaging>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<timestamp>${maven.build.timestamp}</timestamp>
|
||||
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.*
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
net.iharder;version=${base64.version},
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<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 +43,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>
|
||||
@@ -79,39 +60,6 @@
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-ClassPath>.</Bundle-ClassPath>
|
||||
<Bundle-Name>${project.name}</Bundle-Name>
|
||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.keycloak;
|
||||
|
||||
import org.keycloak.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -18,8 +17,9 @@ public class AbstractOAuthClient {
|
||||
protected String clientId;
|
||||
protected Map<String, String> credentials;
|
||||
protected String authUrl;
|
||||
protected String tokenUrl;
|
||||
protected RelativeUrlsUsed relativeUrlsUsed;
|
||||
protected String codeUrl;
|
||||
protected String refreshUrl;
|
||||
protected boolean relativeUrls;
|
||||
protected String scope;
|
||||
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
||||
protected String stateCookiePath;
|
||||
@@ -53,12 +53,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() {
|
||||
@@ -93,12 +101,12 @@ public class AbstractOAuthClient {
|
||||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
public RelativeUrlsUsed getRelativeUrlsUsed() {
|
||||
return relativeUrlsUsed;
|
||||
public boolean isRelativeUrls() {
|
||||
return relativeUrls;
|
||||
}
|
||||
|
||||
public void setRelativeUrlsUsed(RelativeUrlsUsed relativeUrlsUsed) {
|
||||
this.relativeUrlsUsed = relativeUrlsUsed;
|
||||
public void setRelativeUrls(boolean relativeUrls) {
|
||||
this.relativeUrls = relativeUrls;
|
||||
}
|
||||
|
||||
protected String stripOauthParametersFromRedirect(String uri) {
|
||||
|
||||
@@ -121,17 +121,6 @@ public class Config {
|
||||
return v != null ? Boolean.parseBoolean(v) : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope scope(String... scope) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(prefix + ".");
|
||||
for (String s : scope) {
|
||||
sb.append(s);
|
||||
sb.append(".");
|
||||
}
|
||||
return new SystemPropertiesScope(sb.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,7 +146,5 @@ public class Config {
|
||||
|
||||
Boolean getBoolean(String key, Boolean defaultValue);
|
||||
|
||||
Scope scope(String... scope);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,16 @@ import java.security.Principal;
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakPrincipal<T extends KeycloakSecurityContext> implements Principal, Serializable {
|
||||
public class KeycloakPrincipal implements Principal, Serializable {
|
||||
protected final String name;
|
||||
protected final T context;
|
||||
protected final KeycloakSecurityContext context;
|
||||
|
||||
public KeycloakPrincipal(String name, T context) {
|
||||
public KeycloakPrincipal(String name, KeycloakSecurityContext context) {
|
||||
this.name = name;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public T getKeycloakSecurityContext() {
|
||||
public KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@ package org.keycloak;
|
||||
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.util.Base64Url;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -16,12 +11,10 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class KeycloakSecurityContext implements Serializable {
|
||||
protected String tokenString;
|
||||
protected AccessToken token;
|
||||
protected IDToken idToken;
|
||||
protected String idTokenString;
|
||||
|
||||
// Don't store parsed tokens into HTTP session
|
||||
protected transient AccessToken token;
|
||||
protected transient IDToken idToken;
|
||||
|
||||
public KeycloakSecurityContext() {
|
||||
}
|
||||
|
||||
@@ -47,36 +40,4 @@ public class KeycloakSecurityContext implements Serializable {
|
||||
public String getIdTokenString() {
|
||||
return idTokenString;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
// Assumption that issuer contains realm name
|
||||
return token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
// SERIALIZATION
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.defaultWriteObject();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
|
||||
token = parseToken(tokenString, AccessToken.class);
|
||||
idToken = parseToken(idTokenString, IDToken.class);
|
||||
}
|
||||
|
||||
// Just decode token without any verifications
|
||||
private <T> T parseToken(String encoded, Class<T> clazz) throws IOException {
|
||||
if (encoded == null)
|
||||
return null;
|
||||
|
||||
String[] parts = encoded.split("\\.");
|
||||
if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
|
||||
|
||||
byte[] bytes = Base64Url.decode(parts[1]);
|
||||
return JsonSerialization.readValue(bytes, clazz);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.getAudience())) {
|
||||
throw new VerificationException("Token audience doesn't match domain.");
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.keycloak;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ServiceUrlConstants {
|
||||
|
||||
public static final String TOKEN_SERVICE_LOGIN_PATH = "/realms/{realm-name}/tokens/login";
|
||||
public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/realms/{realm-name}/tokens/access/codes";
|
||||
public static final String TOKEN_SERVICE_REFRESH_PATH = "/realms/{realm-name}/tokens/refresh";
|
||||
public static final String TOKEN_SERVICE_LOGOUT_PATH = "/realms/{realm-name}/tokens/logout";
|
||||
public static final String TOKEN_SERVICE_DIRECT_GRANT_PATH = "/realms/{realm-name}/tokens/grants/access";
|
||||
public static final String ACCOUNT_SERVICE_PATH = "/realms/{realm-name}/account";
|
||||
public static final String REALM_INFO_PATH = "/realms/{realm-name}";
|
||||
|
||||
}
|
||||
Loaded 100 of 2140 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user