[KEYCLOAK-883] - Initial changes.

This commit is contained in:
pedroigor 2015-01-13 00:58:19 -02:00
parent 959933a227
commit fa2533ed11
182 changed files with 4502 additions and 2398 deletions

48
broker/core/pom.xml Executable file
View file

@ -0,0 +1,48 @@
<?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-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1-SNAPSHOT</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.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>

View file

@ -0,0 +1,42 @@
/*
* 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;
/**
* @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 void close() {
// no-op
}
}

View file

@ -0,0 +1,51 @@
/*
* 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 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 T create(KeycloakSession session) {
return null;
}
@Override
public Map<String, String> parseConfig(InputStream inputStream) {
return new HashMap<String, String>();
}
}

View file

@ -0,0 +1,76 @@
/*
* 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.RealmModel;
import javax.ws.rs.core.UriInfo;
/**
* @author Pedro Igor
*/
public class AuthenticationRequest {
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(RealmModel realm, ClientSessionModel clientSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
this.realm = realm;
this.httpRequest = httpRequest;
this.uriInfo = uriInfo;
this.state = state;
this.redirectUri = redirectUri;
this.clientSession = clientSession;
}
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;
}
}

View file

@ -0,0 +1,57 @@
/*
* 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 javax.ws.rs.core.Response;
import java.net.URI;
/**
* @author Pedro Igor
*/
public class AuthenticationResponse {
private final Response response;
private final FederatedIdentity user;
private AuthenticationResponse(FederatedIdentity user) {
this.user = user;
this.response = null;
}
private AuthenticationResponse(Response response) {
this.user = null;
this.response = response;
}
public Response getResponse() {
return this.response;
}
public FederatedIdentity getUser() {
return this.user;
}
public static AuthenticationResponse end(FederatedIdentity identity) {
return new AuthenticationResponse(identity);
}
public static AuthenticationResponse temporaryRedirect(URI url) {
return new AuthenticationResponse(Response.temporaryRedirect(url).build());
}
}

View file

@ -0,0 +1,87 @@
/*
* 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;
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;
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.Provider;
/**
* @author Pedro Igor
*/
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
/**
* <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
*/
AuthenticationResponse handleRequest(AuthenticationRequest request);
/**
* <p>Obtains state information sent to the identity provider during the authentication request. Implementations must always
* return the same state in order to check the validity of a response from the identity provider.</p>
*
* <p>This method is invoked on each response from the identity provider.</p>
*
* @param request The request sent by the identity provider in a response to an authentication request.
* @return
*/
String getRelayState(AuthenticationRequest request);
/**
* <p>Handles a response from the identity provider after a successful authentication request is made. Usually, the response will
* contain all the necessary information in order to trust the authentication performed by the identity provider and resolve
* the identity information for the authenticating user.</p>
*
* <p>If the response is trusted and proves user's authenticity, this method may return a
* {@link FederatedIdentity} in the response. In this case, the authentication flow stops.</p>
*
* @param request
* @return
*/
AuthenticationResponse handleResponse(AuthenticationRequest request);
}

View file

@ -0,0 +1,56 @@
/*
* 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);
}

View file

@ -0,0 +1,45 @@
/*
* 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;
}
}

View file

@ -0,0 +1 @@
org.keycloak.broker.provider.IdentityProviderSpi

35
broker/oidc/pom.xml Executable file
View file

@ -0,0 +1,35 @@
<?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-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1-SNAPSHOT</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-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>
</dependencies>
</project>

View file

@ -0,0 +1,156 @@
/*
* 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.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.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Pedro Igor
*/
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> {
public static final String OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
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);
}
@Override
public AuthenticationResponse handleRequest(AuthenticationRequest request) {
try {
URI authorizationUrl = createAuthorizationUrl(request).build();
return AuthenticationResponse.temporaryRedirect(authorizationUrl);
} catch (Exception e) {
throw new RuntimeException("Could not create authentication request.", e);
}
}
@Override
public String getRelayState(AuthenticationRequest request) {
UriInfo uriInfo = request.getUriInfo();
return uriInfo.getQueryParameters().getFirst(OAUTH2_PARAMETER_STATE);
}
@Override
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
UriInfo uriInfo = request.getUriInfo();
String error = uriInfo.getQueryParameters().getFirst(OAuth2Constants.ERROR);
if (error != null) {
if (error.equals("access_denied")) {
throw new RuntimeException("Access denied.");
} else {
throw new RuntimeException(error);
}
}
try {
String authorizationCode = uriInfo.getQueryParameters().getFirst(OAUTH2_PARAMETER_CODE);
if (authorizationCode != null) {
String response = 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, request.getRedirectUri())
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE).asString();
return doHandleResponse(response);
}
throw new RuntimeException("No authorization code from identity provider.");
} catch (Exception e) {
throw new RuntimeException("Could not process response from identity provider.", e);
}
}
protected AuthenticationResponse doHandleResponse(String response) throws IOException {
String token = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
if (token == null) {
throw new RuntimeException("No access token from server.");
}
return AuthenticationResponse.end(getFederatedIdentity(token));
}
protected String extractTokenFromResponse(String response, String tokenName) throws IOException {
if (response.startsWith("{")) {
return mapper.readTree(response).get(tokenName).getTextValue();
} else {
Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
if (matcher.find()) {
return matcher.group(1);
}
}
return null;
}
protected FederatedIdentity getFederatedIdentity(String accessToken) {
throw new RuntimeException("Not implemented.");
};
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
return UriBuilder.fromPath(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);
}
}

View file

@ -0,0 +1,80 @@
/*
* 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;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
public OAuth2IdentityProviderConfig(String providerId, String id, String name, Map<String, String> config) {
super(providerId, id, name, config);
}
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);
}
}

View file

@ -0,0 +1,128 @@
/*
* 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.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.jose.jws.JWSInput;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> {
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
public static final String OIDC_PARAMETER_ID_TOKEN = "id_token";
public OIDCIdentityProvider(OIDCIdentityProviderConfig config) {
super(config);
}
@Override
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
return super.createAuthorizationUrl(request)
.queryParam(OAUTH2_PARAMETER_PROMPT, getConfig().getPrompt());
}
@Override
protected AuthenticationResponse doHandleResponse(String response) throws IOException {
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
if (accessToken == null) {
throw new RuntimeException("No access_token from server.");
}
String idToken = extractTokenFromResponse(response, OIDC_PARAMETER_ID_TOKEN);
validateIdToken(idToken);
try {
JsonNode userInfo = SimpleHttp.doGet(getConfig().getUserInfoUrl())
.header("Authorization", "Bearer " + accessToken)
.asJson();
String id = getJsonProperty(userInfo, "sub");
String name = getJsonProperty(userInfo, "name");
String preferredUsername = getJsonProperty(userInfo, "preferred_username");
String email = getJsonProperty(userInfo, "email");
FederatedIdentity identity = new FederatedIdentity(id);
identity.setId(id);
identity.setName(name);
identity.setEmail(email);
if (preferredUsername == null) {
preferredUsername = email;
}
if (preferredUsername == null) {
preferredUsername = id;
}
identity.setUsername(preferredUsername);
return AuthenticationResponse.end(identity);
} catch (Exception e) {
throw new RuntimeException("Could not fetch attributes from userinfo endpoint.", e);
}
}
private void validateIdToken(String idToken) {
if (idToken == null) {
throw new RuntimeException("No id_token from server.");
}
try {
JsonNode idTokenInfo = asJsonNode(decodeJWS(idToken));
String aud = getJsonProperty(idTokenInfo, "aud");
String iss = getJsonProperty(idTokenInfo, "iss");
if (aud != null && !aud.equals(getConfig().getClientId())) {
throw new RuntimeException("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;
}
}
throw new RuntimeException("Wrong issuer from id_token..");
}
} catch (IOException e) {
throw new RuntimeException("Could not decode id token.", e);
}
}
private String decodeJWS(String token) {
return new JWSInput(token).readContentAsString();
}
}

View file

@ -0,0 +1,56 @@
/*
* 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 java.util.Map;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
public OIDCIdentityProviderConfig(String providerId, String id, String name, Map<String, String> config) {
super(providerId, id, name, config);
}
public String getPrompt() {
String prompt = getConfig().get("prompt");
if (prompt == null || "".equals(prompt)) {
return "none";
}
return prompt;
}
@Override
public String getDefaultScope() {
String scope = super.getDefaultScope();
if (scope == null || "".equals(scope)) {
scope = "openid";
}
return scope;
}
public String getIssuer() {
return getConfig().get("issuer");
}
}

View file

@ -0,0 +1,42 @@
/*
* 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;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<OIDCIdentityProvider> {
@Override
public String getName() {
return "OpenID Connect v1.0";
}
@Override
public OIDCIdentityProvider create(IdentityProviderModel model) {
return new OIDCIdentityProvider(new OIDCIdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
}
@Override
public String getId() {
return "oidc";
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.social.utils;
package org.keycloak.broker.oidc.util;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;

View file

@ -0,0 +1 @@
org.keycloak.broker.oidc.OIDCIdentityProviderFactory

23
broker/pom.xml Executable file
View file

@ -0,0 +1,23 @@
<?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-SNAPSHOT</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>

30
broker/saml/pom.xml Executable file
View file

@ -0,0 +1,30 @@
<?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-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.2.0.Beta1-SNAPSHOT</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.picketlink</groupId>
<artifactId>picketlink-federation</artifactId>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,255 @@
/*
* 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.jboss.resteasy.spi.HttpRequest;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
import org.picketlink.common.constants.JBossSAMLConstants;
import org.picketlink.common.constants.JBossSAMLURIConstants;
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.request.SAML2Request;
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
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.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.assertion.SubjectType.STSubType;
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType.RTChoiceType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusDetailType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusType;
import org.picketlink.identity.federation.web.util.PostBindingUtil;
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.namespace.QName;
import java.net.URI;
import java.net.URLDecoder;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* @author Pedro Igor
*/
public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityProviderConfig> {
private static final String SAML_REQUEST_PARAMETER = "SAMLRequest";
private static final String SAML_RESPONSE_PARAMETER = "SAMLResponse";
private static final String RELAY_STATE_PARAMETER = "RelayState";
private SAML2Signature saml2Signature = new SAML2Signature();
public SAMLIdentityProvider(SAMLIdentityProviderConfig config) {
super(config);
}
@Override
public AuthenticationResponse handleRequest(AuthenticationRequest request) {
try {
UriInfo uriInfo = request.getUriInfo();
String issuerURL = UriBuilder.fromUri(uriInfo.getBaseUri()).build().toString();
String destinationUrl = getConfig().getSingleSignOnServiceUrl();
SAML2Request samlRequest = new SAML2Request();
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
if (nameIDPolicyFormat == null) {
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
}
samlRequest.setNameIDFormat(nameIDPolicyFormat);
AuthnRequestType authn = samlRequest
.createAuthnRequestType(IDGenerator.create("ID_"), request.getRedirectUri(), destinationUrl, issuerURL);
authn.setProtocolBinding(URI.create(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
authn.setForceAuthn(getConfig().isForceAuthn());
Document authnDoc = samlRequest.convert(authn);
if (getConfig().isWantAuthnRequestsSigned()) {
PrivateKey privateKey = request.getRealm().getPrivateKey();
PublicKey publicKey = request.getRealm().getPublicKey();
if (privateKey == null) {
throw new RuntimeException("Identity Provider [" + getConfig().getName() + "] wants a signed authentication request. But the Realm [" + request.getRealm().getName() + "] does not have a private key.");
}
if (publicKey == null) {
throw new RuntimeException("Identity Provider [" + getConfig().getName() + "] wants a signed authentication request. But the Realm [" + request.getRealm().getName() + "] does not have a public key.");
}
KeyPair keypair = new KeyPair(publicKey, privateKey);
this.saml2Signature.signSAMLDocument(authnDoc, keypair);
}
byte[] responseBytes = DocumentUtil.getDocumentAsString(authnDoc).getBytes("UTF-8");
String urlEncodedResponse = RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
URI redirectUri = UriBuilder.fromPath(destinationUrl)
.queryParam(SAML_REQUEST_PARAMETER, urlEncodedResponse)
.queryParam(RELAY_STATE_PARAMETER, request.getState()).build();
return AuthenticationResponse.temporaryRedirect(redirectUri);
} catch (Exception e) {
throw new RuntimeException("Could not create authentication request.", e);
}
}
@Override
public String getRelayState(AuthenticationRequest request) {
HttpRequest httpRequest = request.getHttpRequest();
return httpRequest.getFormParameters().getFirst(RELAY_STATE_PARAMETER);
}
@Override
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
HttpRequest httpRequest = request.getHttpRequest();
String samlResponse = httpRequest.getFormParameters().getFirst(SAML_RESPONSE_PARAMETER);
if (samlResponse == null) {
throw new RuntimeException("No response from SAML identity provider.");
}
try {
SAML2Request saml2Request = new SAML2Request();
ResponseType responseType = (ResponseType) saml2Request
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(samlResponse, "UTF-8")));
AssertionType assertion = getAssertion(request, saml2Request, responseType);
SubjectType subject = assertion.getSubject();
STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
FederatedIdentity user = new FederatedIdentity(subjectNameID.getValue());
user.setUsername(subjectNameID.getValue());
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
user.setEmail(subjectNameID.getValue());
}
return AuthenticationResponse.end(user);
} catch (Exception e) {
throw new RuntimeException("Could not process response from SAML identity provider.", e);
}
}
private AssertionType getAssertion(AuthenticationRequest request, SAML2Request saml2Request, ResponseType responseType) throws ProcessingException {
validateStatusResponse(responseType);
validateSignature(saml2Request);
List<RTChoiceType> assertions = responseType.getAssertions();
if (assertions.isEmpty()) {
throw new RuntimeException("No assertion from response.");
}
RTChoiceType rtChoiceType = assertions.get(0);
EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
if (encryptedAssertion != null) {
decryptAssertion(responseType, request.getRealm().getPrivateKey());
}
return responseType.getAssertions().get(0).getAssertion();
}
private void validateSignature(SAML2Request saml2Request) throws ProcessingException {
if (getConfig().isValidateSignature()) {
X509Certificate certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(getConfig().getSigningPublicKey().replaceAll("\\s", ""));
SAMLDocumentHolder samlDocumentHolder = saml2Request.getSamlDocumentHolder();
Document samlDocument = samlDocumentHolder.getSamlDocument();
this.saml2Signature.validate(samlDocument, certificate.getPublicKey());
}
}
private void validateStatusResponse(ResponseType responseType) {
StatusType status = responseType.getStatus();
StatusCodeType statusCode = status.getStatusCode();
if (!JBossSAMLURIConstants.STATUS_SUCCESS.get().equals(statusCode.getValue().toString())) {
StatusDetailType statusDetailType = status.getStatusDetail();
StringBuilder detailMessage = new StringBuilder();
if (statusDetailType != null) {
for (Object statusDetail : statusDetailType.getAny()) {
detailMessage.append(statusDetail);
}
} else {
detailMessage.append("none");
}
throw new RuntimeException("Authentication failed with code [" + statusCode.getValue() + " and detail [" + detailMessage.toString() + ".");
}
}
private 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 RuntimeException("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 RTChoiceType(assertion));
return responseType;
} catch (Exception e) {
throw new RuntimeException("Could not decrypt assertion.", e);
}
}
}

View file

@ -0,0 +1,92 @@
/*
* 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;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class SAMLIdentityProviderConfig extends IdentityProviderModel {
public SAMLIdentityProviderConfig() {
super();
}
public SAMLIdentityProviderConfig(String providerId, String id, String name, Map<String, String> config) {
super(providerId, id, name, config);
}
public String getSingleSignOnServiceUrl() {
return getConfig().get("singleSignOnServiceUrl");
}
public void setSingleSignOnServiceUrl(String singleSignOnServiceUrl) {
getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
}
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 getSigningPublicKey() {
return getConfig().get("signingPublicKey");
}
public void setSigningPublicKey(String signingPublicKey) {
getConfig().put("signingPublicKey", signingPublicKey);
}
public String getNameIDPolicyFormat() {
return getConfig().get("nameIDPolicyFormat");
}
public void setNameIDPolicyFormat(String signingPublicKey) {
getConfig().put("nameIDPolicyFormat", signingPublicKey);
}
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);
}
}

View file

@ -0,0 +1,125 @@
/*
* 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.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.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> {
@Override
public String getName() {
return "SAML v2.0";
}
@Override
public SAMLIdentityProvider create(IdentityProviderModel model) {
return new SAMLIdentityProvider(new SAMLIdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
}
@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();
samlIdentityProviderConfig.setSingleSignOnServiceUrl(idpDescriptor.getSingleSignOnService().get(0).getLocation().toString());
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
String defaultPublicKey = 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.setSigningPublicKey(x509KeyInfo.getTextContent());
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
} else if (keyDescriptorType.getUse() == null) {
defaultPublicKey = x509KeyInfo.getTextContent();
}
}
}
if (defaultPublicKey != null) {
if (samlIdentityProviderConfig.getSigningPublicKey() == null) {
samlIdentityProviderConfig.setSigningPublicKey(defaultPublicKey);
}
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
samlIdentityProviderConfig.setEncryptionPublicKey(defaultPublicKey);
}
}
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 "saml";
}
}

View file

@ -0,0 +1 @@
org.keycloak.broker.saml.SAMLIdentityProviderFactory

View file

@ -0,0 +1,42 @@
<?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">
<createTable tableName="FEDERATED_IDENTITY">
<column name="IDENTITY_PROVIDER" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="REALM_ID" type="VARCHAR(255)"/>
<column name="FEDERATED_USER_ID" type="VARCHAR(255)"/>
<column name="FEDERATED_USERNAME" type="VARCHAR(255)"/>
<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(1)"/>
<column name="PROVIDER_NONIMAL_ID" type="VARCHAR(255)"/>
<column name="PROVIDER_NAME" type="VARCHAR(255)"/>
<column name="PROVIDER_ID" type="VARCHAR(255)"/>
<column name="UPDATE_PROFILE_FIRST_LOGIN" type="BOOLEAN(1)"/>
<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>
<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"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
</changeSet>
</databaseChangeLog>

View file

@ -3,4 +3,5 @@
<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>

View file

@ -11,12 +11,13 @@
<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.SocialLinkEntity</class>
<class>org.keycloak.models.jpa.entities.FederatedIdentityEntity</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>
<!-- JpaUserSessionProvider -->
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
@ -29,7 +30,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>

View file

@ -21,6 +21,9 @@ public class JWSHeader implements Serializable {
@JsonProperty("cty")
private String contentType;
@JsonProperty("kid")
private String keyId;
public JWSHeader() {
}
@ -42,6 +45,9 @@ public class JWSHeader implements Serializable {
return contentType;
}
public String getKeyId() {
return keyId;
}
private static final ObjectMapper mapper = new ObjectMapper();

View file

@ -0,0 +1,35 @@
package org.keycloak.representations.idm;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FederatedIdentityRepresentation {
protected String identityProvider;
protected String userId;
protected String userName;
public String getIdentityProvider() {
return identityProvider;
}
public void setIdentityProvider(String identityProvider) {
this.identityProvider = identityProvider;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}

View file

@ -0,0 +1,91 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.representations.idm;
import java.util.HashMap;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class IdentityProviderRepresentation {
protected String id;
protected String providerId;
protected String name;
protected boolean enabled = true;
protected boolean updateProfileFirstLogin = true;
protected String groupName;
protected Map<String, String> config = new HashMap<String, String>();
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getProviderId() {
return this.providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getConfig() {
return this.config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
public String getGroupName() {
return this.groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isUpdateProfileFirstLogin() {
return this.updateProfileFirstLogin;
}
public void setUpdateProfileFirstLogin(boolean updateProfileFirstLogin) {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
}

View file

@ -25,8 +25,6 @@ public class RealmRepresentation {
protected Boolean rememberMe;
protected Boolean verifyEmail;
protected Boolean resetPasswordAllowed;
protected Boolean social;
protected Boolean updateProfileOnInitialSocialLogin;
protected Boolean userCacheEnabled;
protected Boolean realmCacheEnabled;
@ -55,7 +53,6 @@ public class RealmRepresentation {
protected List<ApplicationRepresentation> applications;
protected List<OAuthClientRepresentation> oauthClients;
protected Map<String, String> browserSecurityHeaders;
protected Map<String, String> socialProviders;
protected Map<String, String> smtpServer;
protected List<UserFederationProviderRepresentation> userFederationProviders;
protected String loginTheme;
@ -65,6 +62,8 @@ public class RealmRepresentation {
protected Boolean eventsEnabled;
protected Long eventsExpiration;
protected List<String> eventsListeners;
private List<IdentityProviderRepresentation> identityProviders;
private boolean identityFederationEnabled;
public String getId() {
return id;
@ -294,22 +293,6 @@ public class RealmRepresentation {
this.resetPasswordAllowed = resetPassword;
}
public Boolean isSocial() {
return social;
}
public void setSocial(Boolean social) {
this.social = social;
}
public Boolean isUpdateProfileOnInitialSocialLogin() {
return updateProfileOnInitialSocialLogin;
}
public void setUpdateProfileOnInitialSocialLogin(Boolean updateProfileOnInitialSocialLogin) {
this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin;
}
public Map<String, String> getBrowserSecurityHeaders() {
return browserSecurityHeaders;
}
@ -318,14 +301,6 @@ public class RealmRepresentation {
this.browserSecurityHeaders = browserSecurityHeaders;
}
public Map<String, String> getSocialProviders() {
return socialProviders;
}
public void setSocialProviders(Map<String, String> socialProviders) {
this.socialProviders = socialProviders;
}
public Map<String, String> getSmtpServer() {
return smtpServer;
}
@ -485,4 +460,24 @@ public class RealmRepresentation {
public void setUserFederationProviders(List<UserFederationProviderRepresentation> userFederationProviders) {
this.userFederationProviders = userFederationProviders;
}
public List<IdentityProviderRepresentation> getIdentityProviders() {
if (this.identityProviders == null) {
this.identityProviders = new ArrayList<IdentityProviderRepresentation>();
}
return identityProviders;
}
public void setIdentityProviders(List<IdentityProviderRepresentation> identityProviders) {
this.identityProviders = identityProviders;
}
public void addIdentityProvider(IdentityProviderRepresentation identityProviderRepresentation) {
getIdentityProviders().add(identityProviderRepresentation);
}
public boolean isIdentityFederationEnabled() {
return !getIdentityProviders().isEmpty();
}
}

View file

@ -1,35 +0,0 @@
package org.keycloak.representations.idm;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SocialLinkRepresentation {
protected String socialProvider;
protected String socialUserId;
protected String socialUsername;
public String getSocialProvider() {
return socialProvider;
}
public void setSocialProvider(String socialProvider) {
this.socialProvider = socialProvider;
}
public String getSocialUserId() {
return socialUserId;
}
public void setSocialUserId(String socialUserId) {
this.socialUserId = socialUserId;
}
public String getSocialUsername() {
return socialUsername;
}
public void setSocialUsername(String socialUsername) {
this.socialUsername = socialUsername;
}
}

View file

@ -24,7 +24,7 @@ public class UserRepresentation {
protected Map<String, String> attributes;
protected List<CredentialRepresentation> credentials;
protected List<String> requiredActions;
protected List<SocialLinkRepresentation> socialLinks;
protected List<FederatedIdentityRepresentation> federatedIdentities;
protected List<String> realmRoles;
protected Map<String, List<String>> applicationRoles;
@ -139,12 +139,12 @@ public class UserRepresentation {
this.requiredActions = requiredActions;
}
public List<SocialLinkRepresentation> getSocialLinks() {
return socialLinks;
public List<FederatedIdentityRepresentation> getFederatedIdentities() {
return federatedIdentities;
}
public void setSocialLinks(List<SocialLinkRepresentation> socialLinks) {
this.socialLinks = socialLinks;
public void setFederatedIdentities(List<FederatedIdentityRepresentation> federatedIdentities) {
this.federatedIdentities = federatedIdentities;
}
public List<String> getRealmRoles() {

View file

@ -82,7 +82,17 @@
<version>${project.version}</version>
</dependency>
<!-- social -->
<!-- identity providers -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-oidc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-saml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-github</artifactId>

View file

@ -34,7 +34,7 @@ public interface Errors {
String NOT_ALLOWED = "not_allowed";
String SOCIAL_PROVIDER_NOT_FOUND = "social_provider_not_found";
String IDENTITY_PROVIDER_NOT_FOUND = "identity_provider_not_found";
String SOCIAL_ID_IN_USE = "social_id_in_use";
String SSL_REQUIRED = "ssl_required";

View file

@ -19,7 +19,7 @@ public enum EventType {
REFRESH_TOKEN_ERROR,
SOCIAL_LINK,
SOCIAL_LINK_ERROR,
REMOVE_SOCIAL_LINK,
REMOVE_FEDERATED_IDENTITY,
REMOVE_SOCIAL_LINK_ERROR,
UPDATE_EMAIL,

View file

@ -9,8 +9,6 @@
"passwordCredentialGrantAllowed": true,
"sslRequired": "external",
"registrationAllowed": false,
"social": false,
"updateProfileOnInitialSocialLogin": false,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],

View file

@ -8,8 +8,6 @@
"ssoSessionMaxLifespan": 36000,
"sslRequired": "external",
"registrationAllowed": false,
"social": false,
"updateProfileOnInitialSocialLogin": false,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],

View file

@ -9,8 +9,6 @@
"passwordCredentialGrantAllowed": true,
"sslRequired": "external",
"registrationAllowed": false,
"social": false,
"updateProfileOnInitialSocialLogin": false,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],

View file

@ -7,9 +7,7 @@
"accessCodeLifespanUserAction": 6000,
"sslRequired": "external",
"registrationAllowed": false,
"social": false,
"passwordCredentialGrantAllowed": true,
"updateProfileOnInitialSocialLogin": false,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],

View file

@ -7,7 +7,6 @@
"accessCodeLifespanUserAction": 6000,
"sslRequired": "external",
"registrationAllowed": false,
"social": false,
"passwordCredentialGrantAllowed": true,
"updateProfileOnInitialSocialLogin": false,
"privateKey": "MIICXQIBAAKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQABAoGADwFSvEOQuh0IjWRtKZjwjOo4BrmlbRDJ3rf6x2LoemTttSouXzGxx/H87fSZdxNNuU9HbBHoY4ko4POzmZEWhS0gV6UjM7VArc4YjID6Hh2tfU9vCbuuKZrRs7RjxL70b51WxycKc49PQ4JiR3g04punrpq2UzToPrm66zI+ICECQQD2Jauo6cXXoxHR0QychQf4dityZwFXUoR/8oI/YFiu9XwcWgSMwrFKUdWWNKYmrIRNqCBzrGyeiGdaAjsw41T3AkEAyIpn+XL7bek/uLno5/7ULauf2dFI6MEaHJixQJD7S6Tfo/CGuDK93H4K0GAdjgR0LA0tCnB09yyPCd5NmAYKpQJBAO7+BH4s/PsyScr+vs/6GpMTqXuap6KxbBUO0YfXdEPr9mVQwboqDxmp+0esNua1+n+sDlZBw/TpW+/42p/NGmECQF0sOQyjyH+TfGCmN7j6I7ioYZeA7h/9/9TDeK8n7SmDC8kOanlQUfgMs5eG4JRoK1WANaoA/8cLc9XA7EoynGUCQQDx/Gjg6qyWheVujxjKufH1XkqDNiQHClDRM1ntChCmGq/RmpVmce+mYeOYZ9eofv7UJUCBdamllRlB+056Ld2h",

View file

@ -13,7 +13,7 @@ import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation;
@ -25,7 +25,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.io.IOException;
@ -253,14 +253,14 @@ public class ExportUtils {
UserRepresentation userRep = ModelToRepresentation.toRepresentation(user);
// Social links
Set<SocialLinkModel> socialLinks = session.users().getSocialLinks(user, realm);
List<SocialLinkRepresentation> socialLinkReps = new ArrayList<SocialLinkRepresentation>();
for (SocialLinkModel socialLink : socialLinks) {
SocialLinkRepresentation socialLinkRep = exportSocialLink(socialLink);
Set<FederatedIdentityModel> socialLinks = session.users().getFederatedIdentities(user, realm);
List<FederatedIdentityRepresentation> socialLinkReps = new ArrayList<FederatedIdentityRepresentation>();
for (FederatedIdentityModel socialLink : socialLinks) {
FederatedIdentityRepresentation socialLinkRep = exportSocialLink(socialLink);
socialLinkReps.add(socialLinkRep);
}
if (socialLinkReps.size() > 0) {
userRep.setSocialLinks(socialLinkReps);
userRep.setFederatedIdentities(socialLinkReps);
}
// Role mappings
@ -303,11 +303,11 @@ public class ExportUtils {
return userRep;
}
public static SocialLinkRepresentation exportSocialLink(SocialLinkModel socialLink) {
SocialLinkRepresentation socialLinkRep = new SocialLinkRepresentation();
socialLinkRep.setSocialProvider(socialLink.getSocialProvider());
socialLinkRep.setSocialUserId(socialLink.getSocialUserId());
socialLinkRep.setSocialUsername(socialLink.getSocialUsername());
public static FederatedIdentityRepresentation exportSocialLink(FederatedIdentityModel socialLink) {
FederatedIdentityRepresentation socialLinkRep = new FederatedIdentityRepresentation();
socialLinkRep.setIdentityProvider(socialLink.getIdentityProvider());
socialLinkRep.setUserId(socialLink.getUserId());
socialLinkRep.setUserName(socialLink.getUserName());
return socialLinkRep;
}

View file

@ -5,6 +5,6 @@ package org.keycloak.account;
*/
public enum AccountPages {
ACCOUNT, PASSWORD, TOTP, SOCIAL, LOG, SESSIONS;
ACCOUNT, PASSWORD, TOTP, FEDERATED_IDENTITY, LOG, SESSIONS;
}

View file

@ -4,7 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.account.AccountPages;
import org.keycloak.account.AccountProvider;
import org.keycloak.account.freemarker.model.AccountBean;
import org.keycloak.account.freemarker.model.AccountSocialBean;
import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
import org.keycloak.account.freemarker.model.FeaturesBean;
import org.keycloak.account.freemarker.model.LogBean;
import org.keycloak.account.freemarker.model.MessageBean;
@ -51,7 +51,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
private List<Event> events;
private String stateChecker;
private List<UserSessionModel> sessions;
private boolean socialEnabled;
private boolean identityProviderEnabled;
private boolean eventsEnabled;
private boolean passwordUpdateSupported;
private boolean passwordSet;
@ -124,7 +124,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri(), stateChecker));
attributes.put("features", new FeaturesBean(socialEnabled, eventsEnabled, passwordUpdateSupported));
attributes.put("features", new FeaturesBean(identityProviderEnabled, eventsEnabled, passwordUpdateSupported));
switch (page) {
case ACCOUNT:
@ -133,8 +133,8 @@ public class FreeMarkerAccountProvider implements AccountProvider {
case TOTP:
attributes.put("totp", new TotpBean(realm, user, baseUri));
break;
case SOCIAL:
attributes.put("social", new AccountSocialBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
case FEDERATED_IDENTITY:
attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
break;
case LOG:
attributes.put("log", new LogBean(events));
@ -232,8 +232,8 @@ public class FreeMarkerAccountProvider implements AccountProvider {
}
@Override
public AccountProvider setFeatures(boolean socialEnabled, boolean eventsEnabled, boolean passwordUpdateSupported) {
this.socialEnabled = socialEnabled;
public AccountProvider setFeatures(boolean identityProviderEnabled, boolean eventsEnabled, boolean passwordUpdateSupported) {
this.identityProviderEnabled = identityProviderEnabled;
this.eventsEnabled = eventsEnabled;
this.passwordUpdateSupported = passwordUpdateSupported;
return this;

View file

@ -15,8 +15,8 @@ public class Templates {
return "password.ftl";
case TOTP:
return "totp.ftl";
case SOCIAL:
return "social.ftl";
case FEDERATED_IDENTITY:
return "federatedIdentity.ftl";
case LOG:
return "log.ftl";
case SESSIONS:

View file

@ -0,0 +1,114 @@
package org.keycloak.account.freemarker.model;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.flows.Urls;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AccountFederatedIdentityBean {
private final List<FederatedIdentityEntry> identities;
private final boolean removeLinkPossible;
private final KeycloakSession session;
public AccountFederatedIdentityBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
this.session = session;
URI accountIdentityUpdateUri = Urls.accountFederatedIdentityUpdate(baseUri, realm.getName());
this.identities = new LinkedList<FederatedIdentityEntry>();
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
int availableIdentities = 0;
if (identityProviders != null && !identityProviders.isEmpty()) {
for (IdentityProviderModel provider : identityProviders) {
String providerId = provider.getId();
FederatedIdentityModel identity = getIdentity(identities, providerId);
if (identity != null) {
availableIdentities++;
}
String action = identity != null ? "remove" : "add";
String actionUrl = UriBuilder.fromUri(accountIdentityUpdateUri)
.queryParam("action", action)
.queryParam("provider_id", providerId)
.queryParam("stateChecker", stateChecker)
.build().toString();
FederatedIdentityEntry entry = new FederatedIdentityEntry(identity, provider.getName(), actionUrl);
this.identities.add(entry);
}
}
// Removing last social provider is not possible if you don't have other possibility to authenticate
this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(user);
}
private FederatedIdentityModel getIdentity(Set<FederatedIdentityModel> identities, String providerId) {
for (FederatedIdentityModel link : identities) {
if (providerId.equals(link.getIdentityProvider())) {
return link;
}
}
return null;
}
public List<FederatedIdentityEntry> getIdentities() {
return identities;
}
public boolean isRemoveLinkPossible() {
return removeLinkPossible;
}
public class FederatedIdentityEntry {
private FederatedIdentityModel federatedIdentityModel;
private final String providerName;
private final String actionUrl;
public FederatedIdentityEntry(FederatedIdentityModel federatedIdentityModel, String providerName, String actionUrl) {
this.federatedIdentityModel = federatedIdentityModel;
this.providerName = providerName;
this.actionUrl = actionUrl;
}
public String getProviderId() {
return federatedIdentityModel != null ? federatedIdentityModel.getIdentityProvider() : null;
}
public String getProviderName() {
return providerName;
}
public String getUserId() {
return federatedIdentityModel != null ? federatedIdentityModel.getUserId() : null;
}
public String getUserName() {
return federatedIdentityModel != null ? federatedIdentityModel.getUserName() : null;
}
public boolean isConnected() {
return federatedIdentityModel != null;
}
public String getActionUrl() {
return actionUrl;
}
}
}

View file

@ -1,116 +0,0 @@
package org.keycloak.account.freemarker.model;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.SocialLoader;
import org.keycloak.social.SocialProvider;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AccountSocialBean {
private final List<SocialLinkEntry> socialLinks;
private final boolean removeLinkPossible;
private final KeycloakSession session;
public AccountSocialBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
this.session = session;
URI accountSocialUpdateUri = Urls.accountSocialUpdate(baseUri, realm.getName());
this.socialLinks = new LinkedList<SocialLinkEntry>();
Map<String, String> socialConfig = realm.getSocialConfig();
Set<SocialLinkModel> userSocialLinks = session.users().getSocialLinks(user, realm);
int availableLinks = 0;
if (socialConfig != null && !socialConfig.isEmpty()) {
for (SocialProvider provider : SocialLoader.load()) {
String socialProviderId = provider.getId();
if (socialConfig.containsKey(socialProviderId + ".key")) {
SocialLinkModel socialLink = getSocialLink(userSocialLinks, socialProviderId);
if (socialLink != null) {
availableLinks++;
}
String action = socialLink != null ? "remove" : "add";
String actionUrl = UriBuilder.fromUri(accountSocialUpdateUri)
.queryParam("action", action)
.queryParam("provider_id", socialProviderId)
.queryParam("stateChecker", stateChecker)
.build().toString();
SocialLinkEntry entry = new SocialLinkEntry(socialLink, provider.getName(), actionUrl);
this.socialLinks.add(entry);
}
}
}
// Removing last social provider is not possible if you don't have other possibility to authenticate
this.removeLinkPossible = availableLinks > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(user);
}
private SocialLinkModel getSocialLink(Set<SocialLinkModel> userSocialLinks, String socialProviderId) {
for (SocialLinkModel link : userSocialLinks) {
if (socialProviderId.equals(link.getSocialProvider())) {
return link;
}
}
return null;
}
public List<SocialLinkEntry> getLinks() {
return socialLinks;
}
public boolean isRemoveLinkPossible() {
return removeLinkPossible;
}
public class SocialLinkEntry {
private SocialLinkModel link;
private final String providerName;
private final String actionUrl;
public SocialLinkEntry(SocialLinkModel link, String providerName, String actionUrl) {
this.link = link;
this.providerName = providerName;
this.actionUrl = actionUrl;
}
public String getProviderId() {
return link != null ? link.getSocialProvider() : null;
}
public String getProviderName() {
return providerName;
}
public String getSocialUserId() {
return link != null ? link.getSocialUserId() : null;
}
public String getSocialUsername() {
return link != null ? link.getSocialUsername() : null;
}
public boolean isConnected() {
return link != null;
}
public String getActionUrl() {
return actionUrl;
}
}
}

View file

@ -5,18 +5,18 @@ package org.keycloak.account.freemarker.model;
*/
public class FeaturesBean {
private final boolean social;
private final boolean identityFederation;
private final boolean log;
private final boolean passwordUpdateSupported;
public FeaturesBean(boolean social, boolean log, boolean passwordUpdateSupported) {
this.social = social;
public FeaturesBean(boolean identityFederation, boolean log, boolean passwordUpdateSupported) {
this.identityFederation = identityFederation;
this.log = log;
this.passwordUpdateSupported = passwordUpdateSupported;
}
public boolean isSocial() {
return social;
public boolean isIdentityFederation() {
return identityFederation;
}
public boolean isLog() {

View file

@ -3,26 +3,26 @@
<div class="row">
<div class="col-md-10">
<h2>Social Accounts</h2>
<h2>Federated Identities</h2>
</div>
</div>
<form action="${url.passwordUrl}" class="form-horizontal" method="post">
<#list social.links as socialLink>
<#list federatedIdentity.identities as identity>
<div class="form-group">
<div class="col-sm-2 col-md-2">
<label for="${socialLink.providerId!}" class="control-label">${socialLink.providerName!}</label>
<label for="${identity.providerId!}" class="control-label">${identity.providerName!}</label>
</div>
<div class="col-sm-5 col-md-5">
<input disabled="true" class="form-control" value="${socialLink.socialUsername!}">
<input disabled="true" class="form-control" value="${identity.userName!}">
</div>
<div class="col-sm-5 col-md-5">
<#if socialLink.connected>
<#if social.removeLinkPossible>
<a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Remove ${socialLink.providerName!}</a>
<#if identity.connected>
<#if federatedIdentity.removeLinkPossible>
<a href="${identity.actionUrl}" type="submit" class="btn btn-primary btn-lg">Remove ${identity.providerName!}</a>
</#if>
<#else>
<a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Add ${socialLink.providerName!}</a>
<a href="${identity.actionUrl}" type="submit" class="btn btn-primary btn-lg">Add ${identity.providerName!}</a>
</#if>
</div>
</div>

View file

@ -29,13 +29,13 @@ successTotpRemoved=Mobile authenticator removed.
accountUpdated=Your account has been updated
accountPasswordUpdated=Your password has been updated
missingSocialProvider=Social provider not specified
invalidSocialAction=Invalid or missing action
socialProviderNotFound=Specified social provider not found
socialLinkNotActive=This social link is not active anymore
socialRemovingLastProvider=You can't remove last social provider as you don't have password
socialRedirectError=Failed to redirect to social provider
socialProviderRemoved=Social provider removed successfully
missingIdentityProvider=Identity provider not specified
invalidFederatedIdentityAction=Invalid or missing action
identityProviderNotFound=Specified identity provider not found
federatedIdentityLinkNotActive=This identity is not active anymore
federatedIdentityRemovingLastProvider=You can't remove last federated identity as you don't have password
identityProviderRedirectError=Failed to redirect to identity provider
identityProviderRemoved=Identity provider removed successfully
accountDisabled=Account is disabled, contact admin\
accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later

View file

@ -42,7 +42,7 @@
<li class="<#if active=='account'>active</#if>"><a href="${url.accountUrl}">Account</a></li>
<#if features.passwordUpdateSupported><li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li></#if>
<li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
<#if features.social><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
<#if features.identityFederation><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Federated Identity</a></li></#if>
<li class="<#if active=='sessions'>active</#if>"><a href="${url.sessionsUrl}">Sessions</a></li>
<#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">Log</a></li></#if>
</ul>

View file

@ -147,17 +147,41 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmKeysDetailCtrl'
})
.when('/realms/:realm/social-settings', {
templateUrl : 'partials/realm-social.html',
.when('/realms/:realm/identity-provider-settings', {
templateUrl : 'partials/realm-identity-provider.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
},
instance : function(IdentityProviderLoader) {
return {};
},
providerFactory : function(IdentityProviderFactoryLoader) {
return IdentityProviderFactoryLoader();
}
},
controller : 'RealmSocialCtrl'
controller : 'RealmIdentityProviderCtrl'
})
.when('/realms/:realm/identity-provider-settings/provider/:provider_id/:id', {
templateUrl : function(params){ return 'partials/realm-identity-provider-' + params.provider_id + '.html'; },
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
},
instance : function(IdentityProviderLoader) {
return IdentityProviderLoader();
},
providerFactory : function(IdentityProviderFactoryLoader) {
return IdentityProviderFactoryLoader();
}
},
controller : 'RealmIdentityProviderCtrl'
})
.when('/realms/:realm/default-roles', {
templateUrl : 'partials/realm-default-roles.html',
@ -282,8 +306,8 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'UserSessionsCtrl'
})
.when('/realms/:realm/users/:user/social-links', {
templateUrl : 'partials/user-social-links.html',
.when('/realms/:realm/users/:user/federated-identity', {
templateUrl : 'partials/user-federated-identity.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
@ -291,11 +315,11 @@ module.config([ '$routeProvider', function($routeProvider) {
user : function(UserLoader) {
return UserLoader();
},
socialLinks : function(UserSocialLinksLoader) {
return UserSocialLinksLoader();
federatedIdentities : function(UserFederatedIdentityLoader) {
return UserFederatedIdentityLoader();
}
},
controller : 'UserSocialCtrl'
controller : 'UserFederatedIdentityCtrl'
})
.when('/realms/:realm/users', {
templateUrl : 'partials/user-list.html',

View file

@ -258,7 +258,6 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
$scope.realm = angular.copy(realm);
}
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
var oldCopy = angular.copy($scope.realm);
@ -287,7 +286,6 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
}
$location.url("/realms/" + realmCopy.realm);
Notifications.success("The realm has been created.");
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
});
});
@ -307,7 +305,6 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
});
$location.url("/realms/" + realmCopy.realm);
Notifications.success("Your changes have been saved to the realm.");
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
});
}
@ -337,7 +334,6 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, url) {
$scope.realm = angular.copy(realm);
$scope.serverInfo = serverInfo;
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
var oldCopy = angular.copy($scope.realm);
@ -367,7 +363,6 @@ function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $l
});
$location.url(url);
Notifications.success("Your changes have been saved to the realm.");
$scope.social = $scope.realm.social;
$scope.registrationAllowed = $scope.realm.registrationAllowed;
});
};
@ -617,65 +612,156 @@ module.controller('RealmDefaultRolesCtrl', function ($scope, Realm, realm, appli
});
module.controller('RealmSocialCtrl', function($scope, realm, Realm, serverInfo, $location, Notifications) {
console.log('RealmSocialCtrl');
module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications) {
console.log('RealmIdentityProviderCtrl');
$scope.realm = angular.copy(realm);
$scope.hidePassword = true;
$scope.getBoolean = function(value) {
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
} else {
return value;
}
}
if (instance && instance.id) {
$scope.identityProvider = angular.copy(instance);
// fixme: this is a hack to make onofswith work and recognize string representation of boolean values
$scope.identityProvider.config.validateSignature = $scope.getBoolean($scope.identityProvider.config.validateSignature);
$scope.identityProvider.config.forceAuthn = $scope.getBoolean($scope.identityProvider.config.forceAuthn);
$scope.newIdentityProvider = false;
} else {
$scope.identityProvider = {};
$scope.identityProvider.id = providerFactory.id;
$scope.identityProvider.providerId = providerFactory.id;
$scope.identityProvider.name = providerFactory.name;
$scope.identityProvider.enabled = true;
$scope.identityProvider.updateProfileFirstLogin = true;
$scope.newIdentityProvider = true;
}
$scope.serverInfo = serverInfo;
$scope.allProviders = serverInfo.socialProviders;
$scope.configuredProviders = [];
$scope.allProviders = angular.copy(serverInfo.identityProviders);
$scope.$watch('realm.socialProviders', function(socialProviders) {
$scope.configuredProviders = [];
for (var providerConfig in socialProviders) {
var i = providerConfig.split('.');
if (i.length == 2 && i[1] == 'key') {
$scope.configuredProviders.push(i[0]);
}
}
}, true);
$scope.configuredProviders = angular.copy(realm.identityProviders);
var oldCopy = angular.copy($scope.realm);
$scope.changed = false;
$scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/social/callback");
$scope.files = [];
$scope.importFile = false;
$scope.addProvider = function(pId) {
if (!$scope.realm.socialProviders) {
$scope.realm.socialProviders = {};
$scope.onFileSelect = function($files) {
$scope.importFile = true;
$scope.files = $files;
};
$scope.clearFileSelect = function() {
$scope.importFile = false;
$scope.files = null;
}
$scope.uploadFile = function() {
//$files: an array of files selected, each file has name, size, and type.
for (var i = 0; i < $scope.files.length; i++) {
var $file = $scope.files[i];
$scope.upload = $upload.upload({
url: authUrl + '/admin/realms/' + realm.realm + '/identity-provider/',
// method: POST or PUT,
// headers: {'headerKey': 'headerValue'}, withCredential: true,
data: $scope.identityProvider,
file: $file
/* set file formData name for 'Content-Desposition' header. Default: 'file' */
//fileFormDataName: myFile,
/* customize how data is added to formData. See #40#issuecomment-28612000 for example */
//formDataAppender: function(formData, key, val){}
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers) {
$location.url("/realms/" + realm.realm + "/identity-provider-settings");
Notifications.success("The " + $scope.identityProvider.name + " provider has been created.");
}).error(function() {
Notifications.error("The file can not be uploaded. Please verify the file.");
});
}
$scope.realm.socialProviders[pId + ".key"] = "";
$scope.realm.socialProviders[pId + ".secret"] = "";
};
$scope.removeProvider = function(pId) {
delete $scope.realm.socialProviders[pId+".key"];
delete $scope.realm.socialProviders[pId+".secret"];
};
$scope.$watch('configuredProviders', function(configuredProviders) {
if (configuredProviders) {
$scope.configuredProviders = angular.copy(configuredProviders);
$scope.$watch('realm', function() {
if (!angular.equals($scope.realm, oldCopy)) {
$scope.changed = true;
for (var j = 0; j < configuredProviders.length; j++) {
var configProvidedId = configuredProviders[j].providerId;
for (var i in $scope.allProviders) {
var provider = $scope.allProviders[i];
if (provider.groupName == 'Social' && (provider.id == configProvidedId)) {
$scope.allProviders.splice(i, 1);
break;
}
}
}
}
}, true);
$scope.save = function() {
var realmCopy = angular.copy($scope.realm);
realmCopy.social = true;
$scope.changed = false;
Realm.update(realmCopy, function () {
$location.url("/realms/" + realm.realm + "/social-settings");
Notifications.success("The changes have been saved to the realm.");
oldCopy = realmCopy;
$scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/broker/") + realm.realm + "/" ;
$scope.addProvider = function(provider) {
$location.url("/realms/" + realm.realm + "/identity-provider-settings/provider/" + provider.id + "/" + provider.id);
};
$scope.remove = function() {
IdentityProvider.delete({
realm: $scope.realm.realm,
id: $scope.identityProvider.id
}, $scope.identityProvider, function () {
$scope.changed = false;
$location.url("/realms/" + realm.realm + "/identity-provider-settings");
Notifications.success("The " + $scope.identityProvider.name + " provider has been deleted.");
});
};
$scope.reset = function() {
$scope.realm = angular.copy(oldCopy);
$scope.changed = false;
$scope.save = function() {
if ($scope.newIdentityProvider) {
IdentityProvider.create({
realm: $scope.realm.realm
}, $scope.identityProvider, function () {
$location.url("/realms/" + realm.realm + "/identity-provider-settings");
Notifications.success("The " + $scope.identityProvider.name + " provider has been created.");
});
} else {
IdentityProvider.update({
realm: $scope.realm.realm
}, $scope.identityProvider, function () {
$location.url("/realms/" + realm.realm + "/identity-provider-settings");
Notifications.success("The " + $scope.identityProvider.name + " provider has been update.");
});
}
};
$scope.reset = function() {
$scope.identityProvider = {};
$scope.configuredProviders = angular.copy($scope.realm.identityProviders);
};
$scope.showPassword = function(flag) {
$scope.hidePassword = flag;
};
$scope.getBoolean = function(value) {
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
} else {
return value;
}
}
});
module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, TimeUnit) {

View file

@ -129,11 +129,11 @@ module.controller('UserSessionsCtrl', function($scope, realm, user, sessions, Us
}
});
module.controller('UserSocialCtrl', function($scope, realm, user, socialLinks) {
module.controller('UserFederatedIdentityCtrl', function($scope, realm, user, federatedIdentities) {
$scope.realm = realm;
$scope.user = user;
$scope.socialLinks = socialLinks;
console.log('showing social links of user');
$scope.federatedIdentities = federatedIdentities;
console.log('showing federated identities of user');
});

View file

@ -125,8 +125,8 @@ module.factory('UserSessionsLoader', function(Loader, UserSessions, $route, $q)
});
});
module.factory('UserSocialLinksLoader', function(Loader, UserSocialLinks, $route, $q) {
return Loader.query(UserSocialLinks, function() {
module.factory('UserFederatedIdentityLoader', function(Loader, UserFederatedIdentity, $route, $q) {
return Loader.query(UserFederatedIdentity, function() {
return {
realm : $route.current.params.realm,
user : $route.current.params.user
@ -277,3 +277,21 @@ module.factory('OAuthClientInstallationLoader', function(Loader, OAuthClientInst
}
});
});
module.factory('IdentityProviderLoader', function(Loader, IdentityProvider, $route, $q) {
return Loader.get(IdentityProvider, function () {
return {
realm: $route.current.params.realm,
id: $route.current.params.id
}
});
});
module.factory('IdentityProviderFactoryLoader', function(Loader, IdentityProviderFactory, $route, $q) {
return Loader.get(IdentityProviderFactory, function () {
return {
realm: $route.current.params.realm,
provider_id: $route.current.params.provider_id
}
});
});

View file

@ -248,8 +248,8 @@ module.factory('UserLogout', function($resource) {
user : '@user'
});
});
module.factory('UserSocialLinks', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/social-links', {
module.factory('UserFederatedIdentity', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/federated-identity', {
realm : '@realm',
user : '@user'
});
@ -1052,4 +1052,27 @@ module.factory('PasswordPolicy', function() {
};
return p;
});
module.factory('IdentityProvider', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/identity-provider/:id', {
realm : '@realm'
}, {
create : {
method : 'POST'
},
delete : {
method : 'DELETE'
},
update: {
method : 'PUT'
}
});
});
module.factory('IdentityProviderFactory', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/identity-provider/providers/:provider_id', {
realm : '@realm',
provider_id : '@provider_id'
});
});

View file

@ -0,0 +1 @@
<div data-ng-include data-src="'partials/realm-identity-provider-social.html'"></div>

View file

@ -0,0 +1 @@
<div data-ng-include data-src="'partials/realm-identity-provider-social.html'"></div>

View file

@ -0,0 +1 @@
<div data-ng-include data-src="'partials/realm-identity-provider-social.html'"></div>

View file

@ -0,0 +1,117 @@
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-sm-9" role="main">
<data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
<h2></h2>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
<li class="active">{{identityProvider.name}} Provider Settings</li>
</ol>
<h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
<p class="subtitle"><span class="required">*</span> Required fields</p>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="identifier">Alias <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="identifier" type="text" ng-model="identityProvider.id" data-ng-readonly="!newIdentityProvider" required>
</div>
<span tooltip-placement="right" tooltip="The alias unique identifies an identity provider and it is also used to build the redirect uri." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="name">Name <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="name" type="text" ng-model="identityProvider.name" required>
</div>
<span tooltip-placement="right" tooltip="The friendly name for this identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="authorizationUrl">Authorization Url <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="authorizationUrl" type="text" ng-model="identityProvider.config.authorizationUrl" required>
</div>
<span tooltip-placement="right" tooltip="The Authorization Url." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="tokenUrl">Token Url <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="tokenUrl" type="text" ng-model="identityProvider.config.tokenUrl" required>
</div>
<span tooltip-placement="right" tooltip="The Token Url." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="userInfoUrl">User Info Url <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="userInfoUrl" type="text" ng-model="identityProvider.config.userInfoUrl" required>
</div>
<span tooltip-placement="right" tooltip="The User Info Url." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="clientId">Client ID <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.clientId" required>
</div>
<span tooltip-placement="right" tooltip="The client or application identifier registered withing the identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="clientSecret">Client Secret <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="clientSecret" type="password" ng-model="identityProvider.config.clientSecret" ng-show="hidePassword" required>
<input class="form-control" id="clientSecret" type="text" ng-model="identityProvider.config.clientSecret" ng-show="!hidePassword" required>
<a href="" ng-click="showPassword(false)" class="link" ng-show="hidePassword">Show Secret</a>
<a href="" ng-click="showPassword(true);" ng-show="!hidePassword">Hide Secret</a>
</div>
<span tooltip-placement="right" tooltip="The client or application secret registered withing the identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="issuer">Issuer </label>
<div class="col-sm-4">
<input class="form-control" id="issuer" type="text" ng-model="identityProvider.config.issuer">
</div>
<span tooltip-placement="right" tooltip="The issuer identifier for the issuer of the response. If not provided, no validation will be performed." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="defaultScope">Default Scopes </label>
<div class="col-sm-4">
<input class="form-control" id="defaultScope" type="text" ng-model="identityProvider.config.defaultScope">
</div>
<span tooltip-placement="right" tooltip="The scopes to be sent when asking for authorization. It can be a comma-separated list of scopes. Defaults to 'openid'." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="prompt">Prompt</label>
<div class="col-sm-4">
<div class="select-kc">
<select id="prompt" ng-model="identityProvider.config.prompt">
<option value="">none</option>
<option>consent</option>
<option>login</option>
<option>select_account</option>
</select>
</div>
</div>
<span tooltip-placement="right" tooltip="Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Enable/disable this identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="updateProfileFirstLogin">Update Profile on First Login</label>
<div class="col-sm-4">
<input ng-model="identityProvider.updateProfileFirstLogin" name="identityProvider.updateProfileFirstLogin" id="updateProfileFirstLogin" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
</div>
</fieldset>
<div class="pull-right form-actions">
<button kc-save>Save</button>
<button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,100 @@
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-sm-9" role="main">
<data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
<h2></h2>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
<li class="active">{{identityProvider.name}} Provider Settings</li>
</ol>
<h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
<p class="subtitle"><span class="required">*</span> Required fields</p>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="identifier">Alias <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="identifier" type="text" ng-model="identityProvider.id" data-ng-readonly="!newIdentityProvider" required>
</div>
<span tooltip-placement="right" tooltip="The alias unique identifies an identity provider and it is also used to build the redirect uri." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="name">Name <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="name" type="text" ng-model="identityProvider.name" required>
</div>
<span tooltip-placement="right" tooltip="The friendly name for this identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="newIdentityProvider">
<label class="col-sm-2 control-label">Import IdP SAML Metadata </label>
<div class="col-sm-4">
<div class="controls kc-button-input-file" data-ng-show="!files || files.length == 0">
<a href="#" class="btn btn-default"><span class="kc-icon-upload">Icon: Upload</span>Choose a File...</a>
<input id="import-file" type="file" class="transparent" ng-file-select="onFileSelect($files)">
</div>
<span class="kc-uploaded-file" data-ng-show="files.length > 0">
{{files[0].name}}
</span>
</div>
</div>
<div class="form-group clearfix" data-ng-show="!importFile">
<label class="col-sm-2 control-label" for="singleSignOnServiceUrl">Single Sign-On Service Url<span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="singleSignOnServiceUrl" type="text" ng-model="identityProvider.config.singleSignOnServiceUrl" required>
</div>
<span tooltip-placement="right" tooltip="The Url that must be used to send authentication requests(SAML AuthnRequest)." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
<div class="col-sm-4">
<input class="form-control" id="nameIDPolicyFormat" type="text" ng-model="identityProvider.config.nameIDPolicyFormat">
</div>
<span tooltip-placement="right" tooltip="Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix" data-ng-show="!importFile">
<label class="col-sm-2 control-label" for="signingPublicKey">Signing Public Key</label>
<div class="col-sm-4">
<textarea class="form-control" id="signingPublicKey" ng-model="identityProvider.config.signingPublicKey"/>
</div>
<span tooltip-placement="right" tooltip="The public key that must be used to check for signatures." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="forceAuthn">Force Authentication</label>
<div class="col-sm-4">
<input ng-model="identityProvider.config.forceAuthn" id="forceAuthn" onoffswitch />
</div>
<span tooltip-placement="right" tooltip=" Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="validateSignature">Validate Signature</label>
<div class="col-sm-4">
<input ng-model="identityProvider.config.validateSignature" id="validateSignature" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Enable/disable signature validation of SAML responses." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Enable/disable this identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="updateProfileFirstLogin">Update Profile on First Login</label>
<div class="col-sm-4">
<input ng-model="identityProvider.updateProfileFirstLogin" name="identityProvider.updateProfileFirstLogin" id="updateProfileFirstLogin" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
</div>
</fieldset>
<div class="pull-right form-actions">
<button kc-save data-ng-show="!importFile">Save</button>
<button type="submit" data-ng-click="clearFileSelect()" data-ng-show="importFile" class="btn btn-lg btn-default">Cancel</button>
<button type="submit" data-ng-click="uploadFile()" data-ng-show="importFile" class="btn btn-lg btn-primary">Import from SAML Metadata</button>
<button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,68 @@
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-sm-9" role="main">
<data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
<h2></h2>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Social Providers</a></li>
<li class="active">{{identityProvider.name}} Settings</li>
</ol>
<h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
<p class="subtitle"><span class="required">*</span> Required fields</p>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="identifier">Alias <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="identifier" type="text" ng-model="identityProvider.id" required>
</div>
<span tooltip-placement="right" tooltip="The alias unique identifies an identity provider and it is also used to build the redirect uri." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="name">Name <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="name" type="text" ng-model="identityProvider.name" required>
</div>
<span tooltip-placement="right" tooltip="The friendly name for this identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="clientId">Client ID <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="clientId" type="text" ng-model="identityProvider.config.clientId" required>
</div>
<span tooltip-placement="right" tooltip="The client or application identifier registered withing the identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="clientSecret">Client Secret <span class="required">*</span></label>
<div class="col-sm-4">
<input class="form-control" id="clientSecret" type="password" ng-model="identityProvider.config.clientSecret" ng-show="hidePassword" required>
<input class="form-control" id="clientSecret" type="text" ng-model="identityProvider.config.clientSecret" ng-show="!hidePassword" required>
<a href="" ng-click="showPassword(false)" class="link" ng-show="hidePassword">Show Secret</a>
<a href="" ng-click="showPassword(true);" ng-show="!hidePassword">Hide Secret</a>
</div>
<span tooltip-placement="right" tooltip="The client or application secret registered withing the identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Enable/disable this identity provider." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="updateProfileFirstLogin">Update Profile on First Login</label>
<div class="col-sm-4">
<input ng-model="identityProvider.updateProfileFirstLogin" name="identityProvider.updateProfileFirstLogin" id="updateProfileFirstLogin" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
</div>
</fieldset>
<div class="pull-right form-actions">
<button kc-save>Save</button>
<button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1 @@
<div data-ng-include data-src="'partials/realm-identity-provider-social.html'"></div>

View file

@ -3,30 +3,23 @@
<data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
<h2></h2>
<div id="content">
<h2 class="pull-left"><span>{{realm.realm}}</span> Social Providers Settings</h2>
<h2 class="pull-left"><span>{{realm.realm}}</span> Identity Providers Settings</h2>
<p class="subtitle"><span class="required">*</span> Required fields</p>
<div class="alert alert-info alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-info"></span>
<strong>Social Callback URL:</strong> {{callbackUrl}}</p>
</div>
<form name="realmForm" novalidate>
<form name="realmForm" novalidate class="form-horizontal">
<fieldset>
<div>
<table class="table table-striped table-bordered">
<caption class="hidden">Table of social providers</caption>
<caption class="hidden">Table of identity providers</caption>
<thead>
<tr>
<th colspan="5" class="kc-table-actions">
<div class="pull-right">
<div class="select-kc">
<select ng-model="newProviderId"
ng-options="(p|capitalize) for p in (allProviders|remove:configuredProviders)"
data-ng-change="addProvider(newProviderId); newProviderId = null">
<select ng-model="provider"
ng-options="p.name group by p.groupName for p in allProviders track by p.id"
ng-
data-ng-change="addProvider(provider); provider = null">
<option value="" disabled selected>Add provider...</option>
</select>
</div>
@ -34,29 +27,17 @@
</th>
</tr>
<tr ng-show="configuredProviders.length > 0">
<th>Provider</th>
<th>Key <span class="required">*</span></th>
<th>Secret <span class="required">*</span></th>
<th colspan="1">Actions</th>
<th>Name</th>
<th>Redirect URI</th>
</tr>
</thead>
<tbody ng-show="configuredProviders.length > 0">
<tr ng-repeat="pId in configuredProviders">
<tr ng-repeat="identityProvider in configuredProviders">
<td>
<div class="clearfix">
<input class="form-control input-small disabled" type="text" value="{{pId|capitalize}}" readonly>
</div>
<a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.id}}">{{identityProvider.name}}</a>
</td>
<td>
<input class="form-control" type="text" placeholder="Key" ng-model="realm.socialProviders[pId+'.key']"
ng-class="{'dirty': postSaveProviders.indexOf(pId) > -1}" required>
</td>
<td>
<input class="form-control" type="text" placeholder="Secret" ng-model="realm.socialProviders[pId+'.secret']"
ng-class="{'dirty': postSaveProviders.indexOf(pId) > -1}" required>
</td>
<td class="actions">
<div class="action-div"><i class="pficon pficon-delete" ng-click="removeProvider(pId)" tooltip-placement="right" tooltip="Remove Provider"></i></div>
<td ng-show="!changed">
{{callbackUrl}}{{identityProvider.id}}
</td>
</tr>
</tbody>

View file

@ -7,20 +7,6 @@
<h2><span>{{realm.realm}}</span> Login Settings</h2>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset class="border-top">
<div class="form-group">
<label class="col-sm-2 control-label" for="social">Social login</label>
<div class="col-sm-4">
<input ng-model="realm.social" name="social" id="social" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Enable/disable social login via Facebook, Google+, etc." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="realm.social">
<label class="col-sm-2 control-label" for="updateProfileOnInitialSocialLogin">Update profile on first social login</label>
<div class="col-sm-4">
<input ng-model="realm.updateProfileOnInitialSocialLogin" name="updateProfileOnInitialSocialLogin" id="updateProfileOnInitialSocialLogin" onoffswitch />
</div>
<span tooltip-placement="right" tooltip="Should user information from social provider be imported into user database?" class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label for="registrationAllowed" class="col-sm-2 control-label">User registration</label>
<div class="col-sm-4">

View file

@ -5,7 +5,7 @@
<li data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Federated Identities</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">

View file

@ -6,7 +6,7 @@
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">

View file

@ -6,7 +6,7 @@
<li data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="create">
<li class="active"><a href="">User List</a></li>
@ -27,7 +27,7 @@
<p class="subtitle"><span class="required">*</span> Required fields</p>
<form class="form-horizontal" name="userForm" novalidate kc-read-only="!access.manageUsers">
<fieldset class="border-top">
<div class="form-group">
<label class="col-sm-2 control-label"for="id">ID</label>

View file

@ -5,26 +5,26 @@
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">{{user.username}}</a></li>
<li class="active">Social Links</li>
<li class="active">Federated Identities</li>
</ol>
<h2>User <span>{{user.username}}</span> Social Links <span tooltip-placement="right" tooltip="This page shows you if the user was imported from a social login or if it is linked with a particular social login provider." class="fa fa-info-circle"></span></h2>
<h2>User <span>{{user.username}}</span> Federated Identities <span tooltip-placement="right" tooltip="This page shows you all the provisioned identities for an user from trusted identity providers." class="fa fa-info-circle"></span></h2>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Provider Name</th>
<th>Social Username</th>
<th>Identity Provider Name</th>
<th>Username</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="socialLink in socialLinks">
<td>{{socialLink.socialProvider}}</td>
<td>{{socialLink.socialUsername}}</td>
<tr data-ng-repeat="identity in federatedIdentities">
<td>{{identity.identityProvider}}</td>
<td>{{identity.userName}}</td>
</tr>
</tbody>
</table>

View file

@ -5,7 +5,7 @@
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">

View file

@ -1,7 +1,7 @@
<ul class="nav nav-tabs nav-tabs-pf">
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
<li ng-class="{active: path[2] == 'login-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/login-settings">Login</a></li>
<li ng-class="{active: path[2] == 'social-settings'}" data-ng-show="realm.social && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
<li ng-class="{active: path[2] == 'identity-provider-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Provider</a></li>
<li ng-class="{active: path[2] == 'required-credentials'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/required-credentials">Credentials</a></li>
<li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">Keys</a></li>
<li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">Email</a></li>

View file

@ -53,7 +53,8 @@ successTotpRemoved=Mobile authenticator removed.
usernameExists=Username already exists
emailExists=Email already exists
socialEmailExists=User with email already exists. Please login to account management to link the account.
federatedIdentityEmailExists=User with email already exists. Please login to account management to link the account.
federatedIdentityUsernameExists=User with username already exists. Please login to account management to link the account.
loginTitle=Log in to
loginOauthTitle=Temporary access.

View file

@ -20,7 +20,7 @@ import org.keycloak.login.freemarker.model.OAuthGrantBean;
import org.keycloak.login.freemarker.model.ProfileBean;
import org.keycloak.login.freemarker.model.RealmBean;
import org.keycloak.login.freemarker.model.RegisterBean;
import org.keycloak.login.freemarker.model.SocialBean;
import org.keycloak.login.freemarker.model.IdentityProviderBean;
import org.keycloak.login.freemarker.model.TotpBean;
import org.keycloak.login.freemarker.model.UrlBean;
import org.keycloak.models.ClientModel;
@ -188,7 +188,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (realm != null) {
attributes.put("realm", new RealmBean(realm));
attributes.put("social", new SocialBean(realm, baseUri));
attributes.put("social", new IdentityProviderBean(realm, baseUri));
attributes.put("url", new UrlBean(realm, theme, baseUri));
}

View file

@ -21,44 +21,50 @@
*/
package org.keycloak.login.freemarker.model;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.SocialLoader;
import org.keycloak.services.resources.AuthenticationBrokerResource;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class SocialBean {
public class IdentityProviderBean {
private boolean displaySocial;
private List<SocialProvider> providers;
private List<IdentityProvider> providers;
private RealmModel realm;
public SocialBean(RealmModel realm, URI baseURI) {
public IdentityProviderBean(RealmModel realm, URI baseURI) {
this.realm = realm;
Map<String, String> socialConfig = realm.getSocialConfig();
if (realm.isSocial() && !socialConfig.isEmpty()) {
displaySocial = true;
providers = new LinkedList<SocialProvider>();
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
UriBuilder socialLoginUrlBuilder = UriBuilder.fromUri(Urls.socialRedirectToProviderAuth(baseURI, realm.getName()));
for (org.keycloak.social.SocialProvider p : SocialLoader.load()) {
if (socialConfig.containsKey(p.getId() + ".key") && socialConfig.containsKey(p.getId() + ".secret")) {
String loginUrl = socialLoginUrlBuilder.replaceQueryParam("provider_id", p.getId()).build().toString();
providers.add(new SocialProvider(p.getId(), p.getName(), loginUrl));
if (!identityProviders.isEmpty()) {
providers = new LinkedList<IdentityProvider>();
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled()) {
String loginUrl = UriBuilder.fromUri(baseURI)
.path(AuthenticationBrokerResource.class)
.path(AuthenticationBrokerResource.class, "performLogin")
.replaceQueryParam("provider_id", identityProvider.getId())
.build(realm.getName()).toString();
providers.add(new IdentityProvider(identityProvider.getId(), identityProvider.getName(), loginUrl));
}
}
if (!providers.isEmpty()) {
displaySocial = true;
}
}
}
public List<SocialProvider> getProviders() {
public List<IdentityProvider> getProviders() {
return providers;
}
@ -66,18 +72,19 @@ public class SocialBean {
return realm.isRegistrationAllowed() || displaySocial;
}
public boolean isDisplaySocialProviders() {
return displaySocial;
}
public static class IdentityProvider {
public static class SocialProvider {
private final String id;
private final String name;
private final String loginUrl;
private String id;
private String name;
private String loginUrl;
public SocialProvider(String id, String name, String loginUrl) {
public IdentityProvider(String id, String name, String loginUrl) {
this.id = id;
if (name == null) {
name = id;
}
this.name = name;
this.loginUrl = loginUrl;
}
@ -95,5 +102,4 @@ public class SocialBean {
}
}
}

View file

@ -40,8 +40,8 @@ public class RealmBean {
return realm.getName();
}
public boolean isSocial() {
return realm.isSocial();
public boolean isIdentityFederationEnabled() {
return realm.isIdentityFederationEnabled();
}
public boolean isRegistrationAllowed() {
@ -64,5 +64,5 @@ public class RealmBean {
}
return false;
}
}

View file

@ -1,7 +1,7 @@
package org.keycloak.admin.client.resource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
@ -55,11 +55,11 @@ public interface UserResource {
@GET
@Path("social-links")
public List<SocialLinkRepresentation> getSocialLinks();
public List<FederatedIdentityRepresentation> getSocialLinks();
@POST
@Path("social-links/{provider}")
public Response addSocialLink(@PathParam("provider") String provider, SocialLinkRepresentation rep);
public Response addSocialLink(@PathParam("provider") String provider, FederatedIdentityRepresentation rep);
@Path("social-links/{provider}")
@DELETE

View file

@ -0,0 +1,31 @@
package org.keycloak.models;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FederatedIdentityModel {
private String userId;
private String identityProvider;
private String userName;
public FederatedIdentityModel() {};
public FederatedIdentityModel(String identityProvider, String userId, String userName) {
this.userId = userId;
this.identityProvider = identityProvider;
this.userName = userName;
}
public String getUserId() {
return userId;
}
public String getIdentityProvider() {
return identityProvider;
}
public String getUserName() {
return userName;
}
}

View file

@ -0,0 +1,118 @@
/*
* 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.models;
import java.util.HashMap;
import java.util.Map;
/**
* <p>A model type representing the configuration for identity providers. It provides some common properties and also a {@link org.keycloak.models.IdentityProviderModel#config}
* for configuration options and properties specifics to a identity provider.</p>
*
* @author Pedro Igor
*/
public class IdentityProviderModel {
/**
* <p>An user-defined identifier to unique identify an identity provider instance.</p>
*/
private String id;
/**
* <p>An identifier used to reference a specific identity provider implementation. The value of this field is the same
* across instances of the same provider implementation.</p>
*/
private String providerId;
/**
* <p>An user-defined friendly name for an identity provider instance.</p>
*/
private String name;
private boolean enabled;
private boolean updateProfileFirstLogin = true;
/**
* <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
* in the map are understood by the identity provider implementation.</p>
*/
private Map<String, String> config = new HashMap<String, String>();
public IdentityProviderModel() {
this(null, null, null, null);
}
public IdentityProviderModel(String providerId, String id, String name, Map<String, String> config) {
this.providerId = providerId;
this.id = id;
this.name = name;
if (config != null) {
this.config.putAll(config);
}
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getProviderId() {
return this.providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isUpdateProfileFirstLogin() {
return this.updateProfileFirstLogin;
}
public void setUpdateProfileFirstLogin(boolean updateProfileFirstLogin) {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
public Map<String, String> getConfig() {
return this.config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
}

View file

@ -124,9 +124,9 @@ public interface RealmModel extends RoleContainerModel {
RoleModel getRoleById(String id);
List<String> getDefaultRoles();
void addDefaultRole(String name);
void updateDefaultRoles(String[] defaultRoles);
ClientModel findClient(String clientId);
@ -146,14 +146,6 @@ public interface RealmModel extends RoleContainerModel {
void updateRequiredCredentials(Set<String> creds);
boolean isSocial();
void setSocial(boolean social);
boolean isUpdateProfileOnInitialSocialLogin();
void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin);
OAuthClientModel addOAuthClient(String name);
OAuthClientModel addOAuthClient(String id, String name);
@ -171,9 +163,10 @@ public interface RealmModel extends RoleContainerModel {
void setSmtpConfig(Map<String, String> smtpConfig);
Map<String, String> getSocialConfig();
void setSocialConfig(Map<String, String> socialConfig);
List<IdentityProviderModel> getIdentityProviders();
void addIdentityProvider(IdentityProviderModel identityProvider);
void removeIdentityProviderById(String providerId);
void updateIdentityProvider(IdentityProviderModel identityProvider);
List<UserFederationProviderModel> getUserFederationProviders();
@ -228,4 +221,5 @@ public interface RealmModel extends RoleContainerModel {
ClientModel findClientById(String id);
boolean isIdentityFederationEnabled();
}

View file

@ -1,43 +0,0 @@
package org.keycloak.models;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SocialLinkModel {
private String socialUserId;
private String socialProvider;
private String socialUsername;
public SocialLinkModel() {};
public SocialLinkModel(String socialProvider, String socialUserId, String socialUsername) {
this.socialUserId = socialUserId;
this.socialProvider = socialProvider;
this.socialUsername = socialUsername;
}
public String getSocialUserId() {
return socialUserId;
}
public void setSocialUserId(String socialUserId) {
this.socialUserId = socialUserId;
}
public String getSocialProvider() {
return socialProvider;
}
public void setSocialProvider(String socialProvider) {
this.socialProvider = socialProvider;
}
public String getSocialUsername() {
return socialUsername;
}
public void setSocialUsername(String socialUsername) {
this.socialUsername = socialUsername;
}
}

View file

@ -116,16 +116,16 @@ public class UserFederationManager implements UserProvider {
}
@Override
public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
validateUser(realm, user);
session.userStorage().addSocialLink(realm, user, socialLink);
session.userStorage().addFederatedIdentity(realm, user, socialLink);
}
@Override
public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
validateUser(realm, user);
if (user == null) throw new IllegalStateException("Federated user no longer valid");
return session.userStorage().removeSocialLink(realm, user, socialProvider);
return session.userStorage().removeFederatedIdentity(realm, user, socialProvider);
}
@Override
@ -168,8 +168,8 @@ public class UserFederationManager implements UserProvider {
}
@Override
public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
UserModel user = session.userStorage().getUserBySocialLink(socialLink, realm);
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
UserModel user = session.userStorage().getUserByFederatedIdentity(socialLink, realm);
if (user != null) {
user = validateAndProxyUser(realm, user);
}
@ -278,17 +278,17 @@ public class UserFederationManager implements UserProvider {
}
@Override
public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
validateUser(realm, user);
if (user == null) throw new IllegalStateException("Federated user no longer valid");
return session.userStorage().getSocialLinks(user, realm);
return session.userStorage().getFederatedIdentities(user, realm);
}
@Override
public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
validateUser(realm, user);
if (user == null) throw new IllegalStateException("Federated user no longer valid");
return session.userStorage().getSocialLink(user, socialProvider, realm);
return session.userStorage().getFederatedIdentity(user, socialProvider, realm);
}
@Override

View file

@ -17,13 +17,13 @@ public interface UserProvider extends Provider {
UserModel addUser(RealmModel realm, String username);
boolean removeUser(RealmModel realm, UserModel user);
public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink);
public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider);
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider);
UserModel getUserById(String id, RealmModel realm);
UserModel getUserByUsername(String username, RealmModel realm);
UserModel getUserByEmail(String email, RealmModel realm);
UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm);
UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
List<UserModel> getUsers(RealmModel realm);
int getUsersCount(RealmModel realm);
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults);
@ -31,8 +31,8 @@ public interface UserProvider extends Provider {
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm);
SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm);
Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm);
FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm);
void preRemove(RealmModel realm);

View file

@ -0,0 +1,62 @@
package org.keycloak.models.entities;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FederatedIdentityEntity {
private String userId;
private String userName;
private String identityProvider;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getIdentityProvider() {
return identityProvider;
}
public void setIdentityProvider(String identityProvider) {
this.identityProvider = identityProvider;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FederatedIdentityEntity that = (FederatedIdentityEntity) o;
if (identityProvider != null && (that.identityProvider == null || !identityProvider.equals(that.identityProvider))) return false;
if (userId != null && (that.userId == null || !userId.equals(that.userId))) return false;
if (identityProvider == null && that.identityProvider != null)return false;
if (userId == null && that.userId != null) return false;
return true;
}
@Override
public int hashCode() {
int code = 1;
if (userId != null) {
code = code * userId.hashCode() * 13;
}
if (identityProvider != null) {
code = code * identityProvider.hashCode() * 17;
}
return code;
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.models.entities;
import java.util.HashMap;
import java.util.Map;
/**
* @author Pedro Igor
*/
public class IdentityProviderEntity {
private String id;
private String name;
private String iconUrl;
private Map<String, String> config = new HashMap<String, String>();
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getIconUrl() {
return this.iconUrl;
}
public void setIconUrl(String iconUrl) {
this.iconUrl = iconUrl;
}
public Map<String, String> getConfig() {
return this.config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
}

View file

@ -19,7 +19,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private boolean passwordCredentialGrantAllowed;
private boolean resetPasswordAllowed;
private boolean social;
private boolean updateProfileOnInitialSocialLogin;
private String passwordPolicy;
//--- brute force settings
private boolean bruteForceProtected;
@ -128,22 +127,6 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.resetPasswordAllowed = resetPasswordAllowed;
}
public boolean isSocial() {
return social;
}
public void setSocial(boolean social) {
this.social = social;
}
public boolean isUpdateProfileOnInitialSocialLogin() {
return updateProfileOnInitialSocialLogin;
}
public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin;
}
public String getPasswordPolicy() {
return passwordPolicy;
}

View file

@ -1,62 +0,0 @@
package org.keycloak.models.entities;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SocialLinkEntity {
private String socialUserId;
private String socialUsername;
private String socialProvider;
public String getSocialUserId() {
return socialUserId;
}
public void setSocialUserId(String socialUserId) {
this.socialUserId = socialUserId;
}
public String getSocialUsername() {
return socialUsername;
}
public void setSocialUsername(String socialUsername) {
this.socialUsername = socialUsername;
}
public String getSocialProvider() {
return socialProvider;
}
public void setSocialProvider(String socialProvider) {
this.socialProvider = socialProvider;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SocialLinkEntity that = (SocialLinkEntity) o;
if (socialProvider != null && (that.socialProvider == null || !socialProvider.equals(that.socialProvider))) return false;
if (socialUserId != null && (that.socialUserId == null || !socialUserId.equals(that.socialUserId))) return false;
if (socialProvider == null && that.socialProvider != null)return false;
if (socialUserId == null && that.socialUserId != null) return false;
return true;
}
@Override
public int hashCode() {
int code = 1;
if (socialUserId != null) {
code = code * socialUserId.hashCode() * 13;
}
if (socialProvider != null) {
code = code * socialProvider.hashCode() * 17;
}
return code;
}
}

View file

@ -26,7 +26,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
private Map<String, String> attributes;
private List<UserModel.RequiredAction> requiredActions;
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
private List<SocialLinkEntity> socialLinks;
private List<FederatedIdentityEntity> socialLinks;
private String federationLink;
public String getUsername() {
@ -125,11 +125,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
this.credentials = credentials;
}
public List<SocialLinkEntity> getSocialLinks() {
public List<FederatedIdentityEntity> getSocialLinks() {
return socialLinks;
}
public void setSocialLinks(List<SocialLinkEntity> socialLinks) {
public void setSocialLinks(List<FederatedIdentityEntity> socialLinks) {
this.socialLinks = socialLinks;
}

View file

@ -4,12 +4,12 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@ -17,11 +17,12 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.ClaimRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
@ -80,9 +81,7 @@ public class ModelToRepresentation {
rep.setId(realm.getId());
rep.setRealm(realm.getName());
rep.setEnabled(realm.isEnabled());
rep.setSocial(realm.isSocial());
rep.setNotBefore(realm.getNotBefore());
rep.setUpdateProfileOnInitialSocialLogin(realm.isUpdateProfileOnInitialSocialLogin());
rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
rep.setPublicKey(realm.getPublicKeyPem());
if (internal) {
@ -112,7 +111,6 @@ public class ModelToRepresentation {
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
rep.setSmtpServer(realm.getSmtpConfig());
rep.setSocialProviders(realm.getSocialConfig());
rep.setBrowserSecurityHeaders(realm.getBrowserSecurityHeaders());
rep.setAccountTheme(realm.getAccountTheme());
rep.setLoginTheme(realm.getLoginTheme());
@ -146,6 +144,20 @@ public class ModelToRepresentation {
}
rep.setUserFederationProviders(fedProviderReps);
}
for (IdentityProviderModel provider : realm.getIdentityProviders()) {
IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation();
providerRep.setProviderId(provider.getProviderId());
providerRep.setId(provider.getId());
providerRep.setName(provider.getName());
providerRep.setEnabled(provider.isEnabled());
providerRep.setUpdateProfileFirstLogin(provider.isUpdateProfileFirstLogin());
providerRep.setConfig(provider.getConfig());
rep.addIdentityProvider(providerRep);
}
return rep;
}
@ -185,11 +197,11 @@ public class ModelToRepresentation {
return rep;
}
public static SocialLinkRepresentation toRepresentation(SocialLinkModel socialLink) {
SocialLinkRepresentation rep = new SocialLinkRepresentation();
rep.setSocialUsername(socialLink.getSocialUsername());
rep.setSocialProvider(socialLink.getSocialProvider());
rep.setSocialUserId(socialLink.getSocialUserId());
public static FederatedIdentityRepresentation toRepresentation(FederatedIdentityModel socialLink) {
FederatedIdentityRepresentation rep = new FederatedIdentityRepresentation();
rep.setUserName(socialLink.getUserName());
rep.setIdentityProvider(socialLink.getIdentityProvider());
rep.setUserId(socialLink.getUserId());
return rep;
}

View file

@ -7,12 +7,12 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProviderModel;
@ -20,11 +20,11 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.ClaimRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -44,7 +44,6 @@ public class RepresentationToModel {
public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm) {
newRealm.setName(rep.getRealm());
if (rep.isEnabled() != null) newRealm.setEnabled(rep.isEnabled());
if (rep.isSocial() != null) newRealm.setSocial(rep.isSocial());
if (rep.isBruteForceProtected() != null) newRealm.setBruteForceProtected(rep.isBruteForceProtected());
if (rep.getMaxFailureWaitSeconds() != null) newRealm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds());
if (rep.getMinimumQuickLoginWaitSeconds() != null) newRealm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds());
@ -79,8 +78,6 @@ public class RepresentationToModel {
if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
if (rep.isUpdateProfileOnInitialSocialLogin() != null)
newRealm.setUpdateProfileOnInitialSocialLogin(rep.isUpdateProfileOnInitialSocialLogin());
if (rep.getPrivateKey() == null || rep.getPublicKey() == null) {
KeycloakModelUtils.generateRealmKeys(newRealm);
} else {
@ -220,9 +217,6 @@ public class RepresentationToModel {
newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
}
if (rep.getSocialProviders() != null) {
newRealm.setSocialConfig(new HashMap(rep.getSocialProviders()));
}
if (rep.getUserFederationProviders() != null) {
List<UserFederationProviderModel> providerModels = convertFederationProviders(rep.getUserFederationProviders());
newRealm.setUserFederationProviders(providerModels);
@ -242,7 +236,6 @@ public class RepresentationToModel {
realm.setName(rep.getRealm());
}
if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled());
if (rep.isSocial() != null) realm.setSocial(rep.isSocial());
if (rep.isBruteForceProtected() != null) realm.setBruteForceProtected(rep.isBruteForceProtected());
if (rep.getMaxFailureWaitSeconds() != null) realm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds());
if (rep.getMinimumQuickLoginWaitSeconds() != null) realm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds());
@ -255,8 +248,6 @@ public class RepresentationToModel {
if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe());
if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail());
if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
if (rep.isUpdateProfileOnInitialSocialLogin() != null)
realm.setUpdateProfileOnInitialSocialLogin(rep.isUpdateProfileOnInitialSocialLogin());
if (rep.getSslRequired() != null) realm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
if (rep.getAccessCodeLifespan() != null) realm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
if (rep.getAccessCodeLifespanUserAction() != null)
@ -286,10 +277,6 @@ public class RepresentationToModel {
realm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
}
if (rep.getSocialProviders() != null) {
realm.setSocialConfig(new HashMap(rep.getSocialProviders()));
}
if (rep.getBrowserSecurityHeaders() != null) {
realm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders());
}
@ -670,10 +657,10 @@ public class RepresentationToModel {
updateCredential(user, cred);
}
}
if (userRep.getSocialLinks() != null) {
for (SocialLinkRepresentation socialLink : userRep.getSocialLinks()) {
SocialLinkModel mappingModel = new SocialLinkModel(socialLink.getSocialProvider(), socialLink.getSocialUserId(), socialLink.getSocialUsername());
session.users().addSocialLink(newRealm, user, mappingModel);
if (userRep.getFederatedIdentities() != null) {
for (FederatedIdentityRepresentation identity : userRep.getFederatedIdentities()) {
FederatedIdentityModel mappingModel = new FederatedIdentityModel(identity.getIdentityProvider(), identity.getUserId(), identity.getUserName());
session.users().addFederatedIdentity(newRealm, user, mappingModel);
}
}
if (userRep.getRealmRoles() != null) {

View file

@ -4,7 +4,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@ -191,8 +191,8 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
}
@Override
public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
return getDelegate().getUserBySocialLink(socialLink, realm);
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
}
@Override
@ -231,13 +231,13 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
}
@Override
public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
return getDelegate().getSocialLinks(user, realm);
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
return getDelegate().getFederatedIdentities(user, realm);
}
@Override
public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
return getDelegate().getSocialLink(user, socialProvider, realm);
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
return getDelegate().getFederatedIdentity(user, socialProvider, realm);
}
@Override
@ -258,13 +258,13 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
}
@Override
public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
getDelegate().addSocialLink(realm, user, socialLink);
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
getDelegate().addFederatedIdentity(realm, user, socialLink);
}
@Override
public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
return getDelegate().removeSocialLink(realm, user, socialProvider);
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
}
@Override

View file

@ -3,7 +3,7 @@ package org.keycloak.models.cache;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@ -66,8 +66,8 @@ public class NoCacheUserProvider implements CacheUserProvider {
}
@Override
public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
return getDelegate().getUserBySocialLink(socialLink, realm);
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
}
@Override
@ -106,13 +106,13 @@ public class NoCacheUserProvider implements CacheUserProvider {
}
@Override
public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
return getDelegate().getSocialLinks(user, realm);
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
return getDelegate().getFederatedIdentities(user, realm);
}
@Override
public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
return getDelegate().getSocialLink(user, socialProvider, realm);
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
return getDelegate().getFederatedIdentity(user, socialProvider, realm);
}
@Override
@ -131,13 +131,13 @@ public class NoCacheUserProvider implements CacheUserProvider {
}
@Override
public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
getDelegate().addSocialLink(realm, user, socialLink);
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
getDelegate().addFederatedIdentity(realm, user, socialLink);
}
@Override
public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
return getDelegate().removeSocialLink(realm, user, socialProvider);
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
return getDelegate().removeFederatedIdentity(realm, user, socialProvider);
}
@Override

View file

@ -4,6 +4,7 @@ import org.keycloak.Config;
import org.keycloak.enums.SslRequired;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
@ -532,30 +533,6 @@ public class RealmAdapter implements RealmModel {
updated.updateRequiredCredentials(creds);
}
@Override
public boolean isSocial() {
if (updated != null) return updated.isSocial();
return cached.isSocial();
}
@Override
public void setSocial(boolean social) {
getDelegateForUpdate();
updated.setSocial(social);
}
@Override
public boolean isUpdateProfileOnInitialSocialLogin() {
if (updated != null) return updated.isUpdateProfileOnInitialSocialLogin();
return cached.isUpdateProfileOnInitialSocialLogin();
}
@Override
public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
getDelegateForUpdate();
updated.setUpdateProfileOnInitialSocialLogin(updateProfileOnInitialSocialLogin);
}
@Override
public OAuthClientModel addOAuthClient(String name) {
getDelegateForUpdate();
@ -633,15 +610,27 @@ public class RealmAdapter implements RealmModel {
}
@Override
public Map<String, String> getSocialConfig() {
if (updated != null) return updated.getSocialConfig();
return cached.getSocialConfig();
public List<IdentityProviderModel> getIdentityProviders() {
if (updated != null) return updated.getIdentityProviders();
return cached.getIdentityProviders();
}
@Override
public void setSocialConfig(Map<String, String> socialConfig) {
public void addIdentityProvider(IdentityProviderModel identityProvider) {
getDelegateForUpdate();
updated.setSocialConfig(socialConfig);
updated.addIdentityProvider(identityProvider);
}
@Override
public void updateIdentityProvider(IdentityProviderModel identityProvider) {
getDelegateForUpdate();
updated.updateIdentityProvider(identityProvider);
}
@Override
public void removeIdentityProviderById(String providerId) {
getDelegateForUpdate();
updated.removeIdentityProviderById(providerId);
}
@Override
@ -841,6 +830,13 @@ public class RealmAdapter implements RealmModel {
return getOAuthClientById(id);
}
@Override
public boolean isIdentityFederationEnabled() {
if (updated != null) return updated.isIdentityFederationEnabled();
return cached.isIdentityFederationEnabled();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -2,6 +2,7 @@ package org.keycloak.models.cache.entities;
import org.keycloak.enums.SslRequired;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
@ -34,8 +35,7 @@ public class CachedRealm {
private boolean verifyEmail;
private boolean passwordCredentialGrantAllowed;
private boolean resetPasswordAllowed;
private boolean social;
private boolean updateProfileOnInitialSocialLogin;
private boolean identityFederationEnabled;
//--- brute force settings
private boolean bruteForceProtected;
private int maxFailureWaitSeconds;
@ -67,10 +67,10 @@ public class CachedRealm {
private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
private boolean eventsEnabled;
private long eventsExpiration;
@ -93,8 +93,7 @@ public class CachedRealm {
verifyEmail = model.isVerifyEmail();
passwordCredentialGrantAllowed = model.isPasswordCredentialGrantAllowed();
resetPasswordAllowed = model.isResetPasswordAllowed();
social = model.isSocial();
updateProfileOnInitialSocialLogin = model.isUpdateProfileOnInitialSocialLogin();
identityFederationEnabled = model.isIdentityFederationEnabled();
//--- brute force settings
bruteForceProtected = model.isBruteForceProtected();
maxFailureWaitSeconds = model.getMaxFailureWaitSeconds();
@ -125,9 +124,9 @@ public class CachedRealm {
requiredCredentials = model.getRequiredCredentials();
userFederationProviders = model.getUserFederationProviders();
identityProviders = model.getIdentityProviders();
smtpConfig.putAll(model.getSmtpConfig());
socialConfig.putAll(model.getSocialConfig());
browserSecurityHeaders.putAll(model.getBrowserSecurityHeaders());
eventsEnabled = model.isEventsEnabled();
@ -281,22 +280,14 @@ public class CachedRealm {
return passwordPolicy;
}
public boolean isSocial() {
return social;
}
public boolean isUpdateProfileOnInitialSocialLogin() {
return updateProfileOnInitialSocialLogin;
public boolean isIdentityFederationEnabled() {
return identityFederationEnabled;
}
public Map<String, String> getSmtpConfig() {
return smtpConfig;
}
public Map<String, String> getSocialConfig() {
return socialConfig;
}
public Map<String, String> getBrowserSecurityHeaders() {
return browserSecurityHeaders;
}
@ -340,4 +331,8 @@ public class CachedRealm {
public String getCertificatePem() {
return certificatePem;
}
public List<IdentityProviderModel> getIdentityProviders() {
return identityProviders;
}
}

View file

@ -1,15 +1,15 @@
package org.keycloak.models.jpa;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.SocialLinkEntity;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.KeycloakModelUtils;
@ -85,17 +85,17 @@ public class JpaUserProvider implements UserProvider {
private void removeUser(UserEntity user) {
em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteSocialLinkByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteFederatedIdentityByUser").setParameter("user", user).executeUpdate();
em.remove(user);
}
@Override
public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
SocialLinkEntity entity = new SocialLinkEntity();
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) {
FederatedIdentityEntity entity = new FederatedIdentityEntity();
entity.setRealmId(realm.getId());
entity.setSocialProvider(socialLink.getSocialProvider());
entity.setSocialUserId(socialLink.getSocialUserId());
entity.setSocialUsername(socialLink.getSocialUsername());
entity.setIdentityProvider(identity.getIdentityProvider());
entity.setUserId(identity.getUserId());
entity.setUserName(identity.getUserName());
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
entity.setUser(userEntity);
em.persist(entity);
@ -103,8 +103,8 @@ public class JpaUserProvider implements UserProvider {
}
@Override
public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
SocialLinkEntity entity = findSocialLink(user, socialProvider);
public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String identityProvider) {
FederatedIdentityEntity entity = findFederatedIdentity(user, identityProvider);
if (entity != null) {
em.remove(entity);
em.flush();
@ -122,7 +122,7 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserRequiredActionsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteSocialLinkByRealm")
num = em.createNamedQuery("deleteFederatedIdentityByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteCredentialsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
@ -142,7 +142,7 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
num = em.createNamedQuery("deleteSocialLinkByRealmAndLink")
num = em.createNamedQuery("deleteFederatedIdentityByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
@ -199,17 +199,17 @@ public class JpaUserProvider implements UserProvider {
}
@Override
public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
TypedQuery<UserEntity> query = em.createNamedQuery("findUserByLinkAndRealm", UserEntity.class);
public UserModel getUserByFederatedIdentity(FederatedIdentityModel identity, RealmModel realm) {
TypedQuery<UserEntity> query = em.createNamedQuery("findUserByFederatedIdentityAndRealm", UserEntity.class);
query.setParameter("realmId", realm.getId());
query.setParameter("socialProvider", socialLink.getSocialProvider());
query.setParameter("socialUserId", socialLink.getSocialUserId());
query.setParameter("identityProvider", identity.getIdentityProvider());
query.setParameter("userId", identity.getUserId());
List<UserEntity> results = query.getResultList();
if (results.isEmpty()) {
return null;
} else if (results.size() > 1) {
throw new IllegalStateException("More results found for socialProvider=" + socialLink.getSocialProvider() +
", socialUserId=" + socialLink.getSocialUserId() + ", results=" + results);
throw new IllegalStateException("More results found for identityProvider=" + identity.getIdentityProvider() +
", userId=" + identity.getUserId() + ", results=" + results);
} else {
UserEntity user = results.get(0);
return new UserAdapter(realm, em, user);
@ -326,33 +326,33 @@ public class JpaUserProvider implements UserProvider {
return users;
}
private SocialLinkEntity findSocialLink(UserModel user, String socialProvider) {
TypedQuery<SocialLinkEntity> query = em.createNamedQuery("findSocialLinkByUserAndProvider", SocialLinkEntity.class);
private FederatedIdentityEntity findFederatedIdentity(UserModel user, String identityProvider) {
TypedQuery<FederatedIdentityEntity> query = em.createNamedQuery("findFederatedIdentityByUserAndProvider", FederatedIdentityEntity.class);
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
query.setParameter("user", userEntity);
query.setParameter("socialProvider", socialProvider);
List<SocialLinkEntity> results = query.getResultList();
query.setParameter("identityProvider", identityProvider);
List<FederatedIdentityEntity> results = query.getResultList();
return results.size() > 0 ? results.get(0) : null;
}
@Override
public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
TypedQuery<SocialLinkEntity> query = em.createNamedQuery("findSocialLinkByUser", SocialLinkEntity.class);
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
TypedQuery<FederatedIdentityEntity> query = em.createNamedQuery("findFederatedIdentityByUser", FederatedIdentityEntity.class);
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
query.setParameter("user", userEntity);
List<SocialLinkEntity> results = query.getResultList();
Set<SocialLinkModel> set = new HashSet<SocialLinkModel>();
for (SocialLinkEntity entity : results) {
set.add(new SocialLinkModel(entity.getSocialProvider(), entity.getSocialUserId(), entity.getSocialUsername()));
List<FederatedIdentityEntity> results = query.getResultList();
Set<FederatedIdentityModel> set = new HashSet<FederatedIdentityModel>();
for (FederatedIdentityEntity entity : results) {
set.add(new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName()));
}
return set;
}
@Override
public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
SocialLinkEntity entity = findSocialLink(user, socialProvider);
return (entity != null) ? new SocialLinkModel(entity.getSocialProvider(), entity.getSocialUserId(), entity.getSocialUsername()) : null;
public FederatedIdentityModel getFederatedIdentity(UserModel user, String identityProvider, RealmModel realm) {
FederatedIdentityEntity entity = findFederatedIdentity(user, identityProvider);
return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName()) : null;
}
@Override

View file

@ -3,6 +3,7 @@ package org.keycloak.models.jpa;
import org.keycloak.enums.SslRequired;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
@ -11,6 +12,7 @@ import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.jpa.entities.ApplicationEntity;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.OAuthClientEntity;
import org.keycloak.models.jpa.entities.RealmAttributeEntity;
import org.keycloak.models.jpa.entities.RealmEntity;
@ -682,28 +684,6 @@ public class RealmAdapter implements RealmModel {
return getApplicationNameMap().get(name);
}
@Override
public boolean isSocial() {
return realm.isSocial();
}
@Override
public void setSocial(boolean social) {
realm.setSocial(social);
em.flush();
}
@Override
public boolean isUpdateProfileOnInitialSocialLogin() {
return realm.isUpdateProfileOnInitialSocialLogin();
}
@Override
public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
realm.setUpdateProfileOnInitialSocialLogin(updateProfileOnInitialSocialLogin);
em.flush();
}
@Override
public OAuthClientModel addOAuthClient(String name) {
return this.addOAuthClient(KeycloakModelUtils.generateId(), name);
@ -790,17 +770,6 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
@Override
public Map<String, String> getSocialConfig() {
return realm.getSocialConfig();
}
@Override
public void setSocialConfig(Map<String, String> socialConfig) {
realm.setSocialConfig(socialConfig);
em.flush();
}
@Override
public List<UserFederationProviderModel> getUserFederationProviders() {
List<UserFederationProviderEntity> entities = realm.getUserFederationProviders();
@ -1137,4 +1106,67 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
}
@Override
public List<IdentityProviderModel> getIdentityProviders() {
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
for (IdentityProviderEntity entity: realm.getIdentityProviders()) {
IdentityProviderModel identityProviderModel = new IdentityProviderModel(entity.getProviderId(), entity.getId(), entity.getName(),
entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
identityProviders.add(identityProviderModel);
}
return identityProviders;
}
@Override
public void addIdentityProvider(IdentityProviderModel identityProvider) {
IdentityProviderEntity entity = new IdentityProviderEntity();
entity.setInternalId(KeycloakModelUtils.generateId());
entity.setId(identityProvider.getId());
entity.setProviderId(identityProvider.getProviderId());
entity.setName(identityProvider.getName());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
entity.setConfig(identityProvider.getConfig());
realm.addIdentityProvider(entity);
em.persist(entity);
em.flush();
}
@Override
public void removeIdentityProviderById(String providerId) {
for (IdentityProviderEntity entity : realm.getIdentityProviders()) {
if (entity.getId().equals(providerId)) {
em.remove(entity);
em.flush();
}
}
}
@Override
public void updateIdentityProvider(IdentityProviderModel identityProvider) {
for (IdentityProviderEntity entity : this.realm.getIdentityProviders()) {
if (entity.getId().equals(identityProvider.getId())) {
entity.setName(identityProvider.getName());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
entity.setConfig(identityProvider.getConfig());
}
}
em.flush();
}
@Override
public boolean isIdentityFederationEnabled() {
return !this.realm.getIdentityProviders().isEmpty();
}
}

View file

@ -0,0 +1,132 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name= "findFederatedIdentityByUser", query="select link from FederatedIdentityEntity link where link.user = :user"),
@NamedQuery(name= "findFederatedIdentityByUserAndProvider", query="select link from FederatedIdentityEntity link where link.user = :user and link.identityProvider = :identityProvider"),
@NamedQuery(name= "findUserByFederatedIdentityAndRealm", query="select link.user from FederatedIdentityEntity link where link.realmId = :realmId and link.identityProvider = :identityProvider and link.userId = :userId"),
@NamedQuery(name= "deleteFederatedIdentityByRealm", query="delete from FederatedIdentityEntity social where social.user IN (select u from UserEntity u where realmId=:realmId)"),
@NamedQuery(name= "deleteFederatedIdentityByRealmAndLink", query="delete from FederatedIdentityEntity social where social.user IN (select u from UserEntity u where realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name= "deleteFederatedIdentityByUser", query="delete from FederatedIdentityEntity social where social.user = :user")
})
@Table(name="FEDERATED_IDENTITY")
@Entity
@IdClass(FederatedIdentityEntity.Key.class)
public class FederatedIdentityEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "USER_ID")
private UserEntity user;
@Column(name = "REALM_ID")
protected String realmId;
@Id
@Column(name = "IDENTITY_PROVIDER")
protected String identityProvider;
@Column(name = "FEDERATED_USER_ID")
protected String userId;
@Column(name = "FEDERATED_USERNAME")
protected String userName;
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getIdentityProvider() {
return identityProvider;
}
public void setIdentityProvider(String identityProvider) {
this.identityProvider = identityProvider;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public static class Key implements Serializable {
protected UserEntity user;
protected String identityProvider;
public Key() {
}
public Key(UserEntity user, String identityProvider) {
this.user = user;
this.identityProvider = identityProvider;
}
public UserEntity getUser() {
return user;
}
public String getIdentityProvider() {
return identityProvider;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null)
return false;
if (user != null ? !user.getId().equals(key.user != null ? key.user.getId() : null) : key.user != null) return false;
return true;
}
@Override
public int hashCode() {
int result = user != null ? user.getId().hashCode() : 0;
result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
return result;
}
}
}

View file

@ -0,0 +1,114 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import java.util.Map;
/**
* @author Pedro Igor
*/
@Entity
@Table(name="IDENTITY_PROVIDER")
public class IdentityProviderEntity {
@Id
@Column(name="INTERNAL_ID", length = 36)
protected String internalId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "REALM_ID")
protected RealmEntity realm;
@Column(name="PROVIDER_ID")
private String providerId;
@Column(name="PROVIDER_NONIMAL_ID")
private String id;
@Column(name="PROVIDER_NAME")
private String name;
@Column(name="ENABLED")
private boolean enabled;
@Column(name="UPDATE_PROFILE_FIRST_LOGIN")
private boolean updateProfileFirstLogin;
@ElementCollection
@MapKeyColumn(name="name")
@Column(name="value", columnDefinition = "TEXT")
@CollectionTable(name="IDENTITY_PROVIDER_CONFIG", joinColumns={ @JoinColumn(name="IDENTITY_PROVIDER_ID") })
private Map<String, String> config;
public String getInternalId() {
return this.internalId;
}
public void setInternalId(String internalId) {
this.internalId = internalId;
}
public String getProviderId() {
return this.providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public RealmEntity getRealm() {
return this.realm;
}
public void setRealm(RealmEntity realm) {
this.realm = realm;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isUpdateProfileFirstLogin() {
return this.updateProfileFirstLogin;
}
public void setUpdateProfileFirstLogin(boolean updateProfileFirstLogin) {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
public Map<String, String> getConfig() {
return this.config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
}

View file

@ -1,6 +1,5 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@ -54,12 +53,8 @@ public class RealmEntity {
protected boolean verifyEmail;
@Column(name="RESET_PASSWORD_ALLOWED")
protected boolean resetPasswordAllowed;
@Column(name="SOCIAL")
protected boolean social;
@Column(name="REMEMBER_ME")
protected boolean rememberMe;
@Column(name="UPDATE_PROFILE_ON_SOC_LOGIN")
protected boolean updateProfileOnInitialSocialLogin;
@Column(name="PASSWORD_POLICY")
protected String passwordPolicy;
@ -100,7 +95,6 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="FED_PROVIDERS")
List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@ -118,12 +112,6 @@ public class RealmEntity {
@CollectionTable(name="REALM_SMTP_CONFIG", joinColumns={ @JoinColumn(name="REALM_ID") })
protected Map<String, String> smtpConfig = new HashMap<String, String>();
@ElementCollection
@MapKeyColumn(name="NAME")
@Column(name="VALUE")
@CollectionTable(name="REALM_SOCIAL_CONFIG", joinColumns={ @JoinColumn(name="REALM_ID") })
protected Map<String, String> socialConfig = new HashMap<String, String>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="REALM_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="REALM_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
protected Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
@ -142,6 +130,9 @@ public class RealmEntity {
@JoinColumn(name="MASTER_ADMIN_APP")
protected ApplicationEntity masterAdminApp;
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
protected List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
public String getId() {
return id;
}
@ -214,22 +205,6 @@ public class RealmEntity {
this.resetPasswordAllowed = resetPasswordAllowed;
}
public boolean isSocial() {
return social;
}
public void setSocial(boolean social) {
this.social = social;
}
public boolean isUpdateProfileOnInitialSocialLogin() {
return updateProfileOnInitialSocialLogin;
}
public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin;
}
public int getSsoSessionIdleTimeout() {
return ssoSessionIdleTimeout;
}
@ -333,14 +308,6 @@ public class RealmEntity {
this.smtpConfig = smtpConfig;
}
public Map<String, String> getSocialConfig() {
return socialConfig;
}
public void setSocialConfig(Map<String, String> socialConfig) {
this.socialConfig = socialConfig;
}
public Collection<RoleEntity> getDefaultRoles() {
return defaultRoles;
}
@ -452,5 +419,18 @@ public class RealmEntity {
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
public List<IdentityProviderEntity> getIdentityProviders() {
return this.identityProviders;
}
public void setIdentityProviders(List<IdentityProviderEntity> identityProviders) {
this.identityProviders = identityProviders;
}
public void addIdentityProvider(IdentityProviderEntity entity) {
entity.setRealm(this);
getIdentityProviders().add(entity);
}
}

View file

@ -1,132 +0,0 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name="findSocialLinkByUser", query="select link from SocialLinkEntity link where link.user = :user"),
@NamedQuery(name="findSocialLinkByUserAndProvider", query="select link from SocialLinkEntity link where link.user = :user and link.socialProvider = :socialProvider"),
@NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realmId = :realmId and link.socialProvider = :socialProvider and link.socialUserId = :socialUserId"),
@NamedQuery(name="deleteSocialLinkByRealm", query="delete from SocialLinkEntity social where social.user IN (select u from UserEntity u where realmId=:realmId)"),
@NamedQuery(name="deleteSocialLinkByRealmAndLink", query="delete from SocialLinkEntity social where social.user IN (select u from UserEntity u where realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteSocialLinkByUser", query="delete from SocialLinkEntity social where social.user = :user")
})
@Table(name="USER_SOCIAL_LINK")
@Entity
@IdClass(SocialLinkEntity.Key.class)
public class SocialLinkEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "USER_ID")
private UserEntity user;
@Column(name = "REALM_ID")
protected String realmId;
@Id
@Column(name = "SOCIAL_PROVIDER")
protected String socialProvider;
@Column(name = "SOCIAL_USER_ID")
protected String socialUserId;
@Column(name = "SOCIAL_USERNAME")
protected String socialUsername;
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getSocialProvider() {
return socialProvider;
}
public void setSocialProvider(String socialProvider) {
this.socialProvider = socialProvider;
}
public String getSocialUserId() {
return socialUserId;
}
public void setSocialUserId(String socialUserId) {
this.socialUserId = socialUserId;
}
public String getSocialUsername() {
return socialUsername;
}
public void setSocialUsername(String socialUsername) {
this.socialUsername = socialUsername;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public static class Key implements Serializable {
protected UserEntity user;
protected String socialProvider;
public Key() {
}
public Key(UserEntity user, String socialProvider) {
this.user = user;
this.socialProvider = socialProvider;
}
public UserEntity getUser() {
return user;
}
public String getSocialProvider() {
return socialProvider;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (socialProvider != null ? !socialProvider.equals(key.socialProvider) : key.socialProvider != null)
return false;
if (user != null ? !user.getId().equals(key.user != null ? key.user.getId() : null) : key.user != null) return false;
return true;
}
@Override
public int hashCode() {
int result = user != null ? user.getId().hashCode() : 0;
result = 31 * result + (socialProvider != null ? socialProvider.hashCode() : 0);
return result;
}
}
}

View file

@ -9,12 +9,12 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.entities.SocialLinkEntity;
import org.keycloak.models.entities.FederatedIdentityEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.utils.CredentialValidation;
@ -93,10 +93,10 @@ public class MongoUserProvider implements UserProvider {
}
@Override
public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
DBObject query = new QueryBuilder()
.and("socialLinks.socialProvider").is(socialLink.getSocialProvider())
.and("socialLinks.socialUserId").is(socialLink.getSocialUserId())
.and("federatedIdentities.identityProvider").is(socialLink.getIdentityProvider())
.and("federatedIdentities.userId").is(socialLink.getUserId())
.and("realmId").is(realm.getId())
.get();
MongoUserEntity userEntity = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext);
@ -213,35 +213,35 @@ public class MongoUserProvider implements UserProvider {
}
@Override
public Set<SocialLinkModel> getSocialLinks(UserModel userModel, RealmModel realm) {
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
UserModel user = getUserById(userModel.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
List<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
List<FederatedIdentityEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
return Collections.EMPTY_SET;
}
Set<SocialLinkModel> result = new HashSet<SocialLinkModel>();
for (SocialLinkEntity socialLinkEntity : linkEntities) {
SocialLinkModel model = new SocialLinkModel(socialLinkEntity.getSocialProvider(),
socialLinkEntity.getSocialUserId(), socialLinkEntity.getSocialUsername());
Set<FederatedIdentityModel> result = new HashSet<FederatedIdentityModel>();
for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
FederatedIdentityModel model = new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(),
federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName());
result.add(model);
}
return result;
}
private SocialLinkEntity findSocialLink(UserModel userModel, String socialProvider, RealmModel realm) {
private FederatedIdentityEntity findSocialLink(UserModel userModel, String socialProvider, RealmModel realm) {
UserModel user = getUserById(userModel.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
List<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
List<FederatedIdentityEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
return null;
}
for (SocialLinkEntity socialLinkEntity : linkEntities) {
if (socialLinkEntity.getSocialProvider().equals(socialProvider)) {
return socialLinkEntity;
for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
if (federatedIdentityEntity.getIdentityProvider().equals(socialProvider)) {
return federatedIdentityEntity;
}
}
return null;
@ -249,9 +249,9 @@ public class MongoUserProvider implements UserProvider {
@Override
public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
SocialLinkEntity socialLinkEntity = findSocialLink(user, socialProvider, realm);
return socialLinkEntity != null ? new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUserId(), socialLinkEntity.getSocialUsername()) : null;
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
FederatedIdentityEntity federatedIdentityEntity = findSocialLink(user, socialProvider, realm);
return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName()) : null;
}
@Override
@ -296,37 +296,37 @@ public class MongoUserProvider implements UserProvider {
@Override
public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
user = getUserById(user.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
SocialLinkEntity socialLinkEntity = new SocialLinkEntity();
socialLinkEntity.setSocialProvider(socialLink.getSocialProvider());
socialLinkEntity.setSocialUserId(socialLink.getSocialUserId());
socialLinkEntity.setSocialUsername(socialLink.getSocialUsername());
FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
federatedIdentityEntity.setIdentityProvider(socialLink.getIdentityProvider());
federatedIdentityEntity.setUserId(socialLink.getUserId());
federatedIdentityEntity.setUserName(socialLink.getUserName());
getMongoStore().pushItemToList(userEntity, "socialLinks", socialLinkEntity, true, invocationContext);
getMongoStore().pushItemToList(userEntity, "federatedIdentities", federatedIdentityEntity, true, invocationContext);
}
@Override
public boolean removeSocialLink(RealmModel realm, UserModel userModel, String socialProvider) {
public boolean removeFederatedIdentity(RealmModel realm, UserModel userModel, String socialProvider) {
UserModel user = getUserById(userModel.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
SocialLinkEntity socialLinkEntity = findSocialLink(userEntity, socialProvider);
if (socialLinkEntity == null) {
FederatedIdentityEntity federatedIdentityEntity = findSocialLink(userEntity, socialProvider);
if (federatedIdentityEntity == null) {
return false;
}
return getMongoStore().pullItemFromList(userEntity, "socialLinks", socialLinkEntity, invocationContext);
return getMongoStore().pullItemFromList(userEntity, "federatedIdentities", federatedIdentityEntity, invocationContext);
}
private SocialLinkEntity findSocialLink(MongoUserEntity userEntity, String socialProvider) {
List<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
private FederatedIdentityEntity findSocialLink(MongoUserEntity userEntity, String socialProvider) {
List<FederatedIdentityEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
return null;
}
for (SocialLinkEntity socialLinkEntity : linkEntities) {
if (socialLinkEntity.getSocialProvider().equals(socialProvider)) {
return socialLinkEntity;
for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
if (federatedIdentityEntity.getIdentityProvider().equals(socialProvider)) {
return federatedIdentityEntity;
}
}
return null;

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