Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
5917d5885b
122 changed files with 2619 additions and 1827 deletions
|
@ -206,7 +206,7 @@ public class OAuthRequestAuthenticator {
|
|||
tokenStore.saveRequest();
|
||||
log.debug("Sending redirect to login page: " + redirect);
|
||||
exchange.getResponse().setStatus(302);
|
||||
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), false);
|
||||
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
|
||||
exchange.getResponse().setHeader("Location", redirect);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
|
|||
} else {
|
||||
log.debug("Account was not active, returning false");
|
||||
session.removeAttribute(KeycloakUndertowAccount.class.getName());
|
||||
session.removeAttribute(KeycloakSecurityContext.class.getName());
|
||||
session.invalidate(exchange);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.common.util;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class Environment {
|
||||
|
||||
public static final boolean IS_IBM_JAVA = System.getProperty("java.vendor").contains("IBM");
|
||||
|
||||
}
|
|
@ -56,7 +56,7 @@ public abstract class KerberosJdkProvider {
|
|||
return kerberosTicketToGSSCredential(kerberosTicket, GSSCredential.DEFAULT_LIFETIME, GSSCredential.INITIATE_ONLY);
|
||||
}
|
||||
|
||||
// Actually same on both JDKs
|
||||
// Actually can use same on both JDKs
|
||||
public GSSCredential kerberosTicketToGSSCredential(KerberosTicket kerberosTicket, final int lifetime, final int usage) {
|
||||
try {
|
||||
final GSSManager gssManager = GSSManager.getInstance();
|
||||
|
@ -85,7 +85,7 @@ public abstract class KerberosJdkProvider {
|
|||
|
||||
|
||||
public static KerberosJdkProvider getProvider() {
|
||||
if (KerberosSerializationUtils.JAVA_INFO.contains("IBM")) {
|
||||
if (Environment.IS_IBM_JAVA) {
|
||||
return new IBMJDKProvider();
|
||||
} else {
|
||||
return new SunJDKProvider();
|
||||
|
|
8
core/src/main/java/org/keycloak/AbstractOAuthClient.java
Executable file → Normal file
8
core/src/main/java/org/keycloak/AbstractOAuthClient.java
Executable file → Normal file
|
@ -110,6 +110,14 @@ public class AbstractOAuthClient {
|
|||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return isSecure;
|
||||
}
|
||||
|
||||
public void setSecure(boolean secure) {
|
||||
isSecure = secure;
|
||||
}
|
||||
|
||||
public RelativeUrlsUsed getRelativeUrlsUsed() {
|
||||
return relativeUrlsUsed;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.representations;
|
||||
|
||||
/**
|
||||
* Configuration of KeyStore.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
public class KeyStoreConfig {
|
||||
|
||||
protected Boolean realmCertificate;
|
||||
protected String storePassword;
|
||||
protected String keyPassword;
|
||||
protected String keyAlias;
|
||||
protected String realmAlias;
|
||||
protected String format;
|
||||
|
||||
public Boolean isRealmCertificate() {
|
||||
return realmCertificate;
|
||||
}
|
||||
|
||||
public void setRealmCertificate(Boolean realmCertificate) {
|
||||
this.realmCertificate = realmCertificate;
|
||||
}
|
||||
|
||||
public String getStorePassword() {
|
||||
return storePassword;
|
||||
}
|
||||
|
||||
public void setStorePassword(String storePassword) {
|
||||
this.storePassword = storePassword;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return keyPassword;
|
||||
}
|
||||
|
||||
public void setKeyPassword(String keyPassword) {
|
||||
this.keyPassword = keyPassword;
|
||||
}
|
||||
|
||||
public String getKeyAlias() {
|
||||
return keyAlias;
|
||||
}
|
||||
|
||||
public void setKeyAlias(String keyAlias) {
|
||||
this.keyAlias = keyAlias;
|
||||
}
|
||||
|
||||
public String getRealmAlias() {
|
||||
return realmAlias;
|
||||
}
|
||||
|
||||
public void setRealmAlias(String realmAlias) {
|
||||
this.realmAlias = realmAlias;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
}
|
|
@ -128,7 +128,13 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
|
|||
]]></programlisting>
|
||||
Then restart the server.
|
||||
</para>
|
||||
</section>
|
||||
<para>
|
||||
For <literal>keycloak-overlay</literal>, please make sure to use:
|
||||
<programlisting><![CDATA[
|
||||
bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Relational Database Configuration</title>
|
||||
|
|
|
@ -55,6 +55,11 @@
|
|||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-multipart-provider</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.admin.client.resource;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
||||
import org.keycloak.representations.KeyStoreConfig;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
public interface ClientAttributeCertificateResource {
|
||||
|
||||
/**
|
||||
* Get key info
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public CertificateRepresentation getKeyInfo();
|
||||
|
||||
/**
|
||||
* Generate a new certificate with new key pair
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@NoCache
|
||||
@Path("generate")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public CertificateRepresentation generate();
|
||||
|
||||
/**
|
||||
* Upload certificate and eventually private key
|
||||
*
|
||||
* @param uriInfo
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@Path("upload")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input);
|
||||
|
||||
/**
|
||||
* Upload only certificate, not private key
|
||||
*
|
||||
* @param uriInfo
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@Path("upload-certificate")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input);
|
||||
|
||||
/**
|
||||
* Get a keystore file for the client, containing private key and public certificate
|
||||
*
|
||||
* @param config Keystore configuration as JSON
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@NoCache
|
||||
@Path("/download")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public byte[] getKeystore(final KeyStoreConfig config);
|
||||
|
||||
/**
|
||||
* Generate a new keypair and certificate, and get the private key file
|
||||
*
|
||||
* Generates a keypair and certificate and serves the private key in a specified keystore format.
|
||||
* Only generated public certificate is saved in Keycloak DB - the private key is not.
|
||||
*
|
||||
* @param config Keystore configuration as JSON
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@NoCache
|
||||
@Path("/generate-and-download")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public byte[] generateAndGetKeystore(final KeyStoreConfig config);
|
||||
}
|
|
@ -34,7 +34,6 @@ import javax.ws.rs.QueryParam;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author rodrigo.sasaki@icarros.com.br
|
||||
|
@ -55,21 +54,6 @@ public interface ClientResource {
|
|||
@DELETE
|
||||
public void remove();
|
||||
|
||||
@GET
|
||||
@Path("allowed-origins")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Set<String> getAllowedOrigins();
|
||||
|
||||
@PUT
|
||||
@Path("allowed-origins")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void updateAllowedOrigins(Set<String> allowedOrigins);
|
||||
|
||||
@DELETE
|
||||
@Path("allowed-origins")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void removeAllowedOrigins(Set<String> originsToRemove);
|
||||
|
||||
@POST
|
||||
@Path("client-secret")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
@ -80,19 +64,31 @@ public interface ClientResource {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public CredentialRepresentation getSecret();
|
||||
|
||||
/**
|
||||
* Generate a new registration access token for the client
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("registration-access-token")
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public ClientRepresentation regenerateRegistrationAccessToken();
|
||||
|
||||
/**
|
||||
* Get representation of certificate resource
|
||||
*
|
||||
* @param attributePrefix
|
||||
* @return
|
||||
*/
|
||||
@Path("certificates/{attr}")
|
||||
public ClientAttributeCertificateResource getCertficateResource(@PathParam("attr") String attributePrefix);
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("installation/providers/{providerId}")
|
||||
public String getInstallationProvider(@PathParam("providerId") String providerId);
|
||||
|
||||
@POST
|
||||
@Path("logout-all")
|
||||
public void logoutAllUsers();
|
||||
|
||||
@POST
|
||||
@Path("logout-user/{username}")
|
||||
public void logoutUser(@PathParam("username") String username);
|
||||
|
||||
@Path("session-count")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.keycloak.client.registration.cli;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.keycloak.client.registration.ClientRegistration;
|
||||
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
||||
|
||||
|
@ -16,8 +16,8 @@ public class Context {
|
|||
|
||||
private static final ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
|
||||
static {
|
||||
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
|
||||
mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
}
|
||||
|
||||
private ClientRegistration reg;
|
||||
|
|
|
@ -66,8 +66,6 @@ import java.util.Set;
|
|||
* it is added to the cache. So, we keep the version number around for this.
|
||||
* - In a transaction, objects are registered to be invalidated. If an object is marked for invalidation within a transaction
|
||||
* a cached object should never be returned. An DB adapter should always be returned.
|
||||
* - At prepare phase of the transaction, a local lock on the revision cache will be obtained for each object marked for invalidation
|
||||
* we sort the list of these keys to order local acquisition and avoid deadlocks.
|
||||
* - After DB commits, the objects marked for invalidation are invalidated, or rather removed from the cache. At this time
|
||||
* the revision cache entry for this object has its version number bumped.
|
||||
* - Whenever an object is marked for invalidation, the cache is also searched for any objects that are related to this object
|
||||
|
@ -88,15 +86,20 @@ import java.util.Set;
|
|||
* - There is a Infinispan @Listener registered. If an invalidation event happens, this is treated like
|
||||
* the object was removed from the database and will perform evictions based on that assumption.
|
||||
* - Eviction events will also cascade other evictions, but not assume this is a db removal.
|
||||
* - With an invalidation cache, if you remove an entry on node 1 and this entry does not exist on node 2, node 2 will not receive a @Listener invalidation event.
|
||||
* so, hat we have to put a marker entry in the invalidation cache before we read from the DB, so if the DB changes in between reading and adding a cache entry, the cache will be notified and bump
|
||||
* the version information.
|
||||
*
|
||||
* DBs with Repeatable Read:
|
||||
* - DBs like MySQL are Repeatable Read by default. So, if you query a Client for instance, it will always return the same result in the same transaction even if the DB
|
||||
* was updated in between these queries. This makes it possible to store stale cache entries. To avoid this problem, this class stores the current local version counter
|
||||
* at the beginningof the transaction. Whenever an entry is added to the cache, the current coutner is compared against the counter at the beginning of the tx. If the current
|
||||
* is greater, then don't cache.
|
||||
*
|
||||
* Groups and Roles:
|
||||
* - roles are tricky because of composites. Composite lists are cached too. So, when a role is removed
|
||||
* we also iterate and invalidate any role or group that contains that role being removed.
|
||||
*
|
||||
* - Clustering gotchyas. With an invalidation cache, if you remove an entry on node 1 and this entry does not exist on node 2, node 2 will not receive a @Listener invalidation event.
|
||||
* so, hat we have to put a marker entry in the invalidation cache before we read from the DB, so if the DB changes in between reading and adding a cache entry, the cache will be notified and bump
|
||||
* the version information.
|
||||
*
|
||||
* - any relationship should be resolved from session.realms(). For example if JPA.getClientByClientId() is invoked,
|
||||
* JPA should find the id of the client and then call session.realms().getClientById(). THis is to ensure that the cached
|
||||
* object is invoked and all proper invalidation are being invoked.
|
||||
|
@ -124,14 +127,20 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
protected Set<String> invalidations = new HashSet<>();
|
||||
|
||||
protected boolean clearAll;
|
||||
protected final long startupRevision;
|
||||
|
||||
public StreamCacheRealmProvider(StreamRealmCache cache, KeycloakSession session) {
|
||||
this.cache = cache;
|
||||
this.session = session;
|
||||
this.startupRevision = UpdateCounter.current();
|
||||
session.getTransaction().enlistPrepare(getPrepareTransaction());
|
||||
session.getTransaction().enlistAfterCompletion(getAfterTransaction());
|
||||
}
|
||||
|
||||
public long getStartupRevision() {
|
||||
return startupRevision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
|
@ -305,7 +314,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (model == null) return null;
|
||||
if (invalidations.contains(id)) return model;
|
||||
cached = new CachedRealm(loaded, model);
|
||||
cache.addRevisioned(cached);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getDelegate().getRealm(id);
|
||||
} else if (managedRealms.containsKey(id)) {
|
||||
|
@ -329,7 +338,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (model == null) return null;
|
||||
if (invalidations.contains(model.getId())) return model;
|
||||
query = new RealmListQuery(loaded, cacheKey, model.getId());
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (invalidations.contains(cacheKey)) {
|
||||
return getDelegate().getRealmByName(name);
|
||||
|
@ -435,7 +444,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
for (ClientModel client : model) ids.add(client.getId());
|
||||
query = new ClientListQuery(loaded, cacheKey, realm, ids);
|
||||
logger.tracev("adding realm clients cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
List<ClientModel> list = new LinkedList<>();
|
||||
|
@ -508,7 +517,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
for (RoleModel role : model) ids.add(role.getId());
|
||||
query = new RoleListQuery(loaded, cacheKey, realm, ids);
|
||||
logger.tracev("adding realm roles cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
Set<RoleModel> list = new HashSet<>();
|
||||
|
@ -544,7 +553,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
for (RoleModel role : model) ids.add(role.getId());
|
||||
query = new RoleListQuery(loaded, cacheKey, realm, ids, client.getClientId());
|
||||
logger.tracev("adding client roles cache miss: client {0} key {1}", client.getClientId(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
Set<RoleModel> list = new HashSet<>();
|
||||
|
@ -593,7 +602,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (model == null) return null;
|
||||
query = new RoleListQuery(loaded, cacheKey, realm, model.getId());
|
||||
logger.tracev("adding realm role cache miss: client {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
|
||||
|
@ -623,7 +632,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (model == null) return null;
|
||||
query = new RoleListQuery(loaded, cacheKey, realm, model.getId(), client.getClientId());
|
||||
logger.tracev("adding client role cache miss: client {0} key {1}", client.getClientId(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
|
||||
|
@ -660,7 +669,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
} else {
|
||||
cached = new CachedRealmRole(loaded, model, realm);
|
||||
}
|
||||
cache.addRevisioned(cached);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getDelegate().getRoleById(id, realm);
|
||||
|
@ -685,7 +694,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (model == null) return null;
|
||||
if (invalidations.contains(id)) return model;
|
||||
cached = new CachedGroup(loaded, realm, model);
|
||||
cache.addRevisioned(cached);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getDelegate().getGroupById(id, realm);
|
||||
|
@ -725,7 +734,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
for (GroupModel client : model) ids.add(client.getId());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, ids);
|
||||
logger.tracev("adding realm getGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
|
@ -761,7 +770,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
for (GroupModel client : model) ids.add(client.getId());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, ids);
|
||||
logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
}
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
|
@ -837,7 +846,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (invalidations.contains(id)) return model;
|
||||
cached = new CachedClient(loaded, realm, model);
|
||||
logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
|
||||
cache.addRevisioned(cached);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getDelegate().getClientById(id, realm);
|
||||
} else if (managedApplications.containsKey(id)) {
|
||||
|
@ -866,7 +875,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
id = model.getId();
|
||||
query = new ClientListQuery(loaded, cacheKey, realm, id);
|
||||
logger.tracev("adding client by name cache miss: {0}", clientId);
|
||||
cache.addRevisioned(query);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
} else if (invalidations.contains(cacheKey)) {
|
||||
return getDelegate().getClientByClientId(clientId, realm);
|
||||
} else {
|
||||
|
@ -895,7 +904,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
|
|||
if (model == null) return null;
|
||||
if (invalidations.contains(id)) return model;
|
||||
cached = new CachedClientTemplate(loaded, realm, model);
|
||||
cache.addRevisioned(cached);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getDelegate().getClientTemplateById(id, realm);
|
||||
} else if (managedClientTemplates.containsKey(id)) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidat
|
|||
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
|
||||
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedClient;
|
||||
import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
|
||||
|
@ -38,7 +39,6 @@ import org.keycloak.models.cache.infinispan.stream.HasRolePredicate;
|
|||
import org.keycloak.models.cache.infinispan.stream.InClientPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.RealmQueryPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.RoleQueryPredicate;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
@ -73,7 +73,9 @@ public class StreamRealmCache {
|
|||
|
||||
public Long getCurrentRevision(String id) {
|
||||
Long revision = revisions.get(id);
|
||||
if (revision == null) revision = UpdateCounter.current();
|
||||
if (revision == null) {
|
||||
revision = UpdateCounter.current();
|
||||
}
|
||||
// if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
|
||||
// so, we do this to force this.
|
||||
String invalidationKey = "invalidation.key" + id;
|
||||
|
@ -121,7 +123,7 @@ public class StreamRealmCache {
|
|||
Object rev = revisions.put(id, next);
|
||||
}
|
||||
|
||||
public void addRevisioned(Revisioned object) {
|
||||
public void addRevisioned(Revisioned object, long startupRevision) {
|
||||
//startRevisionBatch();
|
||||
String id = object.getId();
|
||||
try {
|
||||
|
@ -135,12 +137,19 @@ public class StreamRealmCache {
|
|||
revisions.startBatch();
|
||||
if (!revisions.getAdvancedCache().lock(id)) {
|
||||
logger.trace("Could not obtain version lock");
|
||||
return;
|
||||
}
|
||||
rev = revisions.get(id);
|
||||
if (rev == null) {
|
||||
if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev2 == null realm.clients");
|
||||
return;
|
||||
}
|
||||
if (rev > startupRevision) { // revision is ahead transaction start. Other transaction updated in the meantime. Don't cache
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracev("Skipped cache. Current revision {0}, Transaction start revision {1}", object.getRevision(), startupRevision);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rev.equals(object.getRevision())) {
|
||||
if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
|
||||
cache.putForExternalRead(id, object);
|
||||
|
|
|
@ -3,6 +3,8 @@ package org.keycloak.models.cache.infinispan;
|
|||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Used to track cache revisions
|
||||
*
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UpdateCounter {
|
||||
|
|
|
@ -270,7 +270,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
@Override
|
||||
public void removeUserSession(RealmModel realm, UserSessionModel session) {
|
||||
UserSessionEntity entity = getUserSessionEntity(session, false);
|
||||
removeUserSession(realm, entity, false);
|
||||
if (entity != null) {
|
||||
removeUserSession(realm, entity, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -553,7 +555,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
return ((UserSessionAdapter) userSession).getEntity();
|
||||
} else {
|
||||
Cache<String, SessionEntity> cache = getCache(offline);
|
||||
return (UserSessionEntity) cache.get(userSession.getId());
|
||||
return cache != null ? (UserSessionEntity) cache.get(userSession.getId()) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -578,7 +580,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
@Override
|
||||
public void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession) {
|
||||
UserSessionEntity userSessionEntity = getUserSessionEntity(userSession, true);
|
||||
removeUserSession(realm, userSessionEntity, true);
|
||||
if (userSessionEntity != null) {
|
||||
removeUserSession(realm, userSessionEntity, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -47,7 +47,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClientAdapter implements ClientModel {
|
||||
public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
||||
|
||||
protected KeycloakSession session;
|
||||
protected RealmModel realm;
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.models.ProtocolMapperModel;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
|
||||
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
|
@ -42,7 +43,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClientTemplateAdapter implements ClientTemplateModel {
|
||||
public class ClientTemplateAdapter implements ClientTemplateModel , JpaModel<ClientTemplateEntity> {
|
||||
|
||||
protected KeycloakSession session;
|
||||
protected RealmModel realm;
|
||||
|
|
|
@ -41,7 +41,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class GroupAdapter implements GroupModel {
|
||||
public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
|
||||
|
||||
protected GroupEntity group;
|
||||
protected EntityManager em;
|
||||
|
@ -53,7 +53,7 @@ public class GroupAdapter implements GroupModel {
|
|||
this.realm = realm;
|
||||
}
|
||||
|
||||
public GroupEntity getGroup() {
|
||||
public GroupEntity getEntity() {
|
||||
return group;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ public class GroupAdapter implements GroupModel {
|
|||
|
||||
public static GroupEntity toEntity(GroupModel model, EntityManager em) {
|
||||
if (model instanceof GroupAdapter) {
|
||||
return ((GroupAdapter)model).getGroup();
|
||||
return ((GroupAdapter)model).getEntity();
|
||||
}
|
||||
return em.getReference(GroupEntity.class, model.getId());
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ public class GroupAdapter implements GroupModel {
|
|||
|
||||
protected TypedQuery<GroupRoleMappingEntity> getGroupRoleMappingEntityTypedQuery(RoleModel role) {
|
||||
TypedQuery<GroupRoleMappingEntity> query = em.createNamedQuery("groupHasRole", GroupRoleMappingEntity.class);
|
||||
query.setParameter("group", getGroup());
|
||||
query.setParameter("group", getEntity());
|
||||
query.setParameter("roleId", role.getId());
|
||||
return query;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ public class GroupAdapter implements GroupModel {
|
|||
public void grantRole(RoleModel role) {
|
||||
if (hasRole(role)) return;
|
||||
GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
|
||||
entity.setGroup(getGroup());
|
||||
entity.setGroup(getEntity());
|
||||
entity.setRoleId(role.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
|
@ -269,7 +269,7 @@ public class GroupAdapter implements GroupModel {
|
|||
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
|
||||
// even if we're getting just the id.
|
||||
TypedQuery<String> query = em.createNamedQuery("groupRoleMappingIds", String.class);
|
||||
query.setParameter("group", getGroup());
|
||||
query.setParameter("group", getEntity());
|
||||
List<String> ids = query.getResultList();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (String roleId : ids) {
|
||||
|
|
9
model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java
Executable file
9
model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java
Executable file
|
@ -0,0 +1,9 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface JpaModel<T> {
|
||||
T getEntity();
|
||||
}
|
|
@ -39,9 +39,11 @@ import javax.persistence.EntityManager;
|
|||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -53,7 +55,6 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
private final KeycloakSession session;
|
||||
protected EntityManager em;
|
||||
|
||||
|
||||
public JpaRealmProvider(KeycloakSession session, EntityManager em) {
|
||||
this.session = session;
|
||||
this.em = em;
|
||||
|
@ -76,21 +77,22 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
realm.setId(id);
|
||||
em.persist(realm);
|
||||
em.flush();
|
||||
final RealmModel model = new RealmAdapter(session, em, realm);
|
||||
final RealmModel adapter = new RealmAdapter(session, em, realm);
|
||||
session.getKeycloakSessionFactory().publish(new RealmModel.RealmCreationEvent() {
|
||||
@Override
|
||||
public RealmModel getCreatedRealm() {
|
||||
return model;
|
||||
return adapter;
|
||||
}
|
||||
});
|
||||
return model;
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
RealmEntity realm = em.find(RealmEntity.class, id);
|
||||
if (realm == null) return null;
|
||||
return new RealmAdapter(session, em, realm);
|
||||
RealmAdapter adapter = new RealmAdapter(session, em, realm);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,6 +126,7 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
if (realm == null) {
|
||||
return false;
|
||||
}
|
||||
em.refresh(realm);
|
||||
RealmAdapter adapter = new RealmAdapter(session, em, realm);
|
||||
session.users().preRemove(adapter);
|
||||
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
|
||||
|
@ -174,7 +177,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
entity.setRealmId(realm.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
return new RoleAdapter(session, realm, em, entity);
|
||||
RoleAdapter adapter = new RoleAdapter(session, realm, em, entity);
|
||||
return adapter;
|
||||
|
||||
}
|
||||
|
||||
|
@ -203,7 +207,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
roleEntity.setRealmId(realm.getId());
|
||||
em.persist(roleEntity);
|
||||
em.flush();
|
||||
return new RoleAdapter(session, realm, em, roleEntity);
|
||||
RoleAdapter adapter = new RoleAdapter(session, realm, em, roleEntity);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -247,11 +252,11 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
@Override
|
||||
public boolean removeRole(RealmModel realm, RoleModel role) {
|
||||
session.users().preRemove(realm, role);
|
||||
RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container.getDefaultRoles().contains(role.getName())) {
|
||||
container.removeDefaultRoles(role.getName());
|
||||
}
|
||||
RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
|
||||
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
|
||||
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
|
||||
em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
|
||||
|
@ -260,7 +265,6 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
|
||||
em.remove(roleEntity);
|
||||
em.flush();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
@ -270,7 +274,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
RoleEntity entity = em.find(RoleEntity.class, id);
|
||||
if (entity == null) return null;
|
||||
if (!realm.getId().equals(entity.getRealmId())) return null;
|
||||
return new RoleAdapter(session, realm, em, entity);
|
||||
RoleAdapter adapter = new RoleAdapter(session, realm, em, entity);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -278,7 +283,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
GroupEntity groupEntity = em.find(GroupEntity.class, id);
|
||||
if (groupEntity == null) return null;
|
||||
if (!groupEntity.getRealm().getId().equals(realm.getId())) return null;
|
||||
return new GroupAdapter(realm, em, groupEntity);
|
||||
GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -361,7 +367,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
groupEntity.setRealm(realmEntity);
|
||||
em.persist(groupEntity);
|
||||
|
||||
return new GroupAdapter(realm, em, groupEntity);
|
||||
GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -391,6 +398,7 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
em.persist(entity);
|
||||
em.flush();
|
||||
final ClientModel resource = new ClientAdapter(realm, em, session, entity);
|
||||
|
||||
em.flush();
|
||||
session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
|
||||
@Override
|
||||
|
@ -416,14 +424,14 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
ClientEntity app = em.find(ClientEntity.class, id);
|
||||
|
||||
// Check if application belongs to this realm
|
||||
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
|
||||
return new ClientAdapter(realm, em, session, app);
|
||||
ClientAdapter client = new ClientAdapter(realm, em, session, app);
|
||||
return client;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -468,6 +476,7 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
|
||||
// Check if application belongs to this realm
|
||||
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
|
||||
return new ClientTemplateAdapter(realm, em, session, app);
|
||||
ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RealmAdapter implements RealmModel {
|
||||
public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||
protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
|
||||
protected RealmEntity realm;
|
||||
protected EntityManager em;
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.jpa.entities.RealmEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
|
@ -33,7 +34,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RoleAdapter implements RoleModel {
|
||||
public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
|
||||
protected RoleEntity role;
|
||||
protected EntityManager em;
|
||||
protected RealmModel realm;
|
||||
|
@ -46,7 +47,7 @@ public class RoleAdapter implements RoleModel {
|
|||
this.session = session;
|
||||
}
|
||||
|
||||
public RoleEntity getRole() {
|
||||
public RoleEntity getEntity() {
|
||||
return role;
|
||||
}
|
||||
|
||||
|
@ -97,17 +98,17 @@ public class RoleAdapter implements RoleModel {
|
|||
@Override
|
||||
public void addCompositeRole(RoleModel role) {
|
||||
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
|
||||
for (RoleEntity composite : getRole().getCompositeRoles()) {
|
||||
for (RoleEntity composite : getEntity().getCompositeRoles()) {
|
||||
if (composite.equals(entity)) return;
|
||||
}
|
||||
getRole().getCompositeRoles().add(entity);
|
||||
getEntity().getCompositeRoles().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompositeRole(RoleModel role) {
|
||||
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
|
||||
Iterator<RoleEntity> it = getRole().getCompositeRoles().iterator();
|
||||
Iterator<RoleEntity> it = getEntity().getCompositeRoles().iterator();
|
||||
while (it.hasNext()) {
|
||||
if (it.next().equals(entity)) it.remove();
|
||||
}
|
||||
|
@ -117,7 +118,7 @@ public class RoleAdapter implements RoleModel {
|
|||
public Set<RoleModel> getComposites() {
|
||||
Set<RoleModel> set = new HashSet<RoleModel>();
|
||||
|
||||
for (RoleEntity composite : getRole().getCompositeRoles()) {
|
||||
for (RoleEntity composite : getEntity().getCompositeRoles()) {
|
||||
set.add(new RoleAdapter(session, realm, em, composite));
|
||||
|
||||
// todo I want to do this, but can't as you get stack overflow
|
||||
|
@ -161,7 +162,7 @@ public class RoleAdapter implements RoleModel {
|
|||
|
||||
public static RoleEntity toRoleEntity(RoleModel model, EntityManager em) {
|
||||
if (model instanceof RoleAdapter) {
|
||||
return ((RoleAdapter)model).getRole();
|
||||
return ((RoleAdapter)model).getEntity();
|
||||
}
|
||||
return em.getReference(RoleEntity.class, model.getId());
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserAdapter implements UserModel {
|
||||
public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||
|
||||
protected UserEntity user;
|
||||
protected EntityManager em;
|
||||
|
@ -77,7 +77,7 @@ public class UserAdapter implements UserModel {
|
|||
this.session = session;
|
||||
}
|
||||
|
||||
public UserEntity getUser() {
|
||||
public UserEntity getEntity() {
|
||||
return user;
|
||||
}
|
||||
|
||||
|
@ -503,7 +503,7 @@ public class UserAdapter implements UserModel {
|
|||
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
|
||||
// even if we're getting just the id.
|
||||
TypedQuery<String> query = em.createNamedQuery("userGroupIds", String.class);
|
||||
query.setParameter("user", getUser());
|
||||
query.setParameter("user", getEntity());
|
||||
List<String> ids = query.getResultList();
|
||||
Set<GroupModel> groups = new HashSet<>();
|
||||
for (String groupId : ids) {
|
||||
|
@ -518,7 +518,7 @@ public class UserAdapter implements UserModel {
|
|||
public void joinGroup(GroupModel group) {
|
||||
if (isMemberOf(group)) return;
|
||||
UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
|
||||
entity.setUser(getUser());
|
||||
entity.setUser(getEntity());
|
||||
entity.setGroupId(group.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
|
@ -548,7 +548,7 @@ public class UserAdapter implements UserModel {
|
|||
|
||||
protected TypedQuery<UserGroupMembershipEntity> getUserGroupMappingQuery(GroupModel group) {
|
||||
TypedQuery<UserGroupMembershipEntity> query = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class);
|
||||
query.setParameter("user", getUser());
|
||||
query.setParameter("user", getEntity());
|
||||
query.setParameter("groupId", group.getId());
|
||||
return query;
|
||||
}
|
||||
|
@ -562,7 +562,7 @@ public class UserAdapter implements UserModel {
|
|||
|
||||
protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(RoleModel role) {
|
||||
TypedQuery<UserRoleMappingEntity> query = em.createNamedQuery("userHasRole", UserRoleMappingEntity.class);
|
||||
query.setParameter("user", getUser());
|
||||
query.setParameter("user", getEntity());
|
||||
query.setParameter("roleId", role.getId());
|
||||
return query;
|
||||
}
|
||||
|
@ -571,7 +571,7 @@ public class UserAdapter implements UserModel {
|
|||
public void grantRole(RoleModel role) {
|
||||
if (hasRole(role)) return;
|
||||
UserRoleMappingEntity entity = new UserRoleMappingEntity();
|
||||
entity.setUser(getUser());
|
||||
entity.setUser(getEntity());
|
||||
entity.setRoleId(role.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
|
@ -598,7 +598,7 @@ public class UserAdapter implements UserModel {
|
|||
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
|
||||
// even if we're getting just the id.
|
||||
TypedQuery<String> query = em.createNamedQuery("userRoleMappingIds", String.class);
|
||||
query.setParameter("user", getUser());
|
||||
query.setParameter("user", getEntity());
|
||||
List<String> ids = query.getResultList();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (String roleId : ids) {
|
||||
|
|
|
@ -50,6 +50,7 @@ import java.util.Set;
|
|||
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
|
||||
@NamedQuery(name="getClientById", query="select client from ClientEntity client where client.id = :id and client.realm.id = :realm"),
|
||||
@NamedQuery(name="getClientIdsByRealm", query="select client.id from ClientEntity client where client.realm.id = :realm"),
|
||||
@NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
|
||||
@NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
|
||||
|
|
|
@ -19,5 +19,6 @@
|
|||
<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="keycloak" id="1.9.1">
|
||||
<modifyDataType tableName="REALM" columnName="PRIVATE_KEY" newDataType="VARCHAR(4096)"/>
|
||||
<modifyDataType tableName="REALM" columnName="CERTIFICATE" newDataType="VARCHAR(4096)"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.saml.common.parsers;
|
||||
|
||||
import org.keycloak.common.util.Environment;
|
||||
import org.keycloak.saml.common.PicketLinkLogger;
|
||||
import org.keycloak.saml.common.PicketLinkLoggerFactory;
|
||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||
|
@ -29,6 +30,8 @@ import javax.xml.stream.XMLInputFactory;
|
|||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.events.Characters;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
import javax.xml.stream.util.EventReaderDelegate;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
@ -83,28 +86,10 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
|
|||
if (configStream == null)
|
||||
throw logger.nullArgumentError("InputStream");
|
||||
|
||||
XMLInputFactory xmlInputFactory = getXMLInputFactory();
|
||||
|
||||
XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(configStream);
|
||||
|
||||
try {
|
||||
xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
|
||||
public boolean accept(XMLEvent xmlEvent) {
|
||||
// We are going to disregard characters that are new line and whitespace
|
||||
if (xmlEvent.isCharacters()) {
|
||||
Characters chars = xmlEvent.asCharacters();
|
||||
String data = chars.getData();
|
||||
data = valid(data) ? data.trim() : null;
|
||||
return valid(data);
|
||||
} else {
|
||||
return xmlEvent.isStartElement() || xmlEvent.isEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean valid(String str) {
|
||||
return str != null && str.length() > 0;
|
||||
}
|
||||
});
|
||||
xmlEventReader = filterWhitespaces(xmlEventReader);
|
||||
} catch (XMLStreamException e) {
|
||||
throw logger.parserException(e);
|
||||
}
|
||||
|
@ -137,4 +122,48 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
|
|||
}
|
||||
}
|
||||
|
||||
protected XMLEventReader filterWhitespaces(XMLEventReader xmlEventReader) throws XMLStreamException {
|
||||
XMLInputFactory xmlInputFactory = getXMLInputFactory();
|
||||
|
||||
xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
|
||||
public boolean accept(XMLEvent xmlEvent) {
|
||||
// We are going to disregard characters that are new line and whitespace
|
||||
if (xmlEvent.isCharacters()) {
|
||||
Characters chars = xmlEvent.asCharacters();
|
||||
String data = chars.getData();
|
||||
data = valid(data) ? data.trim() : null;
|
||||
return valid(data);
|
||||
} else {
|
||||
return xmlEvent.isStartElement() || xmlEvent.isEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean valid(String str) {
|
||||
return str != null && str.length() > 0;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Handle IBM JDK bug with Stax parsing when EventReader presented
|
||||
if (Environment.IS_IBM_JAVA) {
|
||||
final XMLEventReader origReader = xmlEventReader;
|
||||
|
||||
xmlEventReader = new EventReaderDelegate(origReader) {
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
boolean hasNext = super.hasNext();
|
||||
try {
|
||||
return hasNext && (origReader.peek() != null);
|
||||
} catch (XMLStreamException xse) {
|
||||
throw new IllegalStateException(xse);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
return xmlEventReader;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,7 @@ import org.w3c.dom.Document;
|
|||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBElement;
|
||||
import javax.xml.bind.util.JAXBSource;
|
||||
|
@ -108,6 +109,21 @@ public class TransformerUtil {
|
|||
SecurityActions.setTCCL(TransformerUtil.class.getClassLoader());
|
||||
}
|
||||
transformerFactory = TransformerFactory.newInstance();
|
||||
try {
|
||||
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
} catch (TransformerConfigurationException ignored) {
|
||||
// some platforms don't support this. For example our testsuite pulls Selenium which requires Xalan 2.7.1
|
||||
logger.warn("XML External Entity switches are not supported. You may get XML injection vulnerabilities.");
|
||||
}
|
||||
try {
|
||||
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||
|
||||
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
|
||||
} catch (Exception ignored) {
|
||||
// some platforms don't support this. For example our testsuite pulls Selenium which requires Xalan 2.7.1
|
||||
logger.warn("XML External Entity switches are not supported. You may get XML injection vulnerabilities.");
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (tccl_jaxp) {
|
||||
SecurityActions.setTCCL(prevTCCL);
|
||||
|
|
|
@ -35,28 +35,8 @@ import javax.xml.stream.events.XMLEvent;
|
|||
public abstract class AbstractDescriptorParser extends AbstractParser {
|
||||
|
||||
protected XMLEventReader filterWhiteSpaceCharacters(XMLEventReader xmlEventReader) throws ParsingException {
|
||||
|
||||
XMLInputFactory xmlInputFactory = getXMLInputFactory();
|
||||
|
||||
try {
|
||||
xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
|
||||
public boolean accept(XMLEvent xmlEvent) {
|
||||
// We are going to disregard characters that are new line and whitespace
|
||||
if (xmlEvent.isCharacters()) {
|
||||
Characters chars = xmlEvent.asCharacters();
|
||||
String data = chars.getData();
|
||||
data = valid(data) ? data.trim() : null;
|
||||
return valid(data);
|
||||
} else {
|
||||
return xmlEvent.isStartElement() || xmlEvent.isEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean valid(String str) {
|
||||
return str != null && str.length() > 0;
|
||||
}
|
||||
});
|
||||
return xmlEventReader;
|
||||
return filterWhitespaces(xmlEventReader);
|
||||
} catch (XMLStreamException e) {
|
||||
throw new ParsingException(e);
|
||||
}
|
||||
|
|
|
@ -55,9 +55,7 @@ public class UserFederationManager implements UserProvider {
|
|||
}
|
||||
|
||||
protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
|
||||
UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
|
||||
return factory.getInstance(session, model);
|
||||
|
||||
return KeycloakModelUtils.getFederationProviderInstance(session, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -35,6 +35,8 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.ScopeContainerModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationProviderFactory;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
|
@ -252,18 +254,21 @@ public final class KeycloakModelUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Try to find user by given username. If it fails, then fallback to find him by email
|
||||
* Try to find user by username or email
|
||||
*
|
||||
* @param realm realm
|
||||
* @param username username or email of user
|
||||
* @return found user
|
||||
*/
|
||||
public static UserModel findUserByNameOrEmail(KeycloakSession session, RealmModel realm, String username) {
|
||||
UserModel user = session.users().getUserByUsername(username, realm);
|
||||
if (user == null && username.contains("@")) {
|
||||
user = session.users().getUserByEmail(username, realm);
|
||||
if (username.indexOf('@') != -1) {
|
||||
UserModel user = session.users().getUserByEmail(username, realm);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return user;
|
||||
|
||||
return session.users().getUserByUsername(username, realm);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -406,6 +411,12 @@ public final class KeycloakModelUtils {
|
|||
return mapperModel;
|
||||
}
|
||||
|
||||
public static UserFederationProvider getFederationProviderInstance(KeycloakSession session, UserFederationProviderModel model) {
|
||||
UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
|
||||
return factory.getInstance(session, model);
|
||||
|
||||
}
|
||||
|
||||
// END USER FEDERATION RELATED STUFF
|
||||
|
||||
public static String toLowerCaseSafe(String str) {
|
||||
|
|
|
@ -569,7 +569,7 @@ public class AuthenticationProcessor {
|
|||
} else if (e.getError() == AuthenticationFlowError.USER_TEMPORARILY_DISABLED) {
|
||||
logger.failedAuthentication(e);
|
||||
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED);
|
||||
return ErrorPage.error(session, Messages.INVALID_USER);
|
||||
|
||||
} else if (e.getError() == AuthenticationFlowError.INVALID_CLIENT_SESSION) {
|
||||
logger.failedAuthentication(e);
|
||||
|
|
|
@ -65,7 +65,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
|
|||
|
||||
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
|
||||
return context.form()
|
||||
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
|
||||
.setError(Messages.INVALID_USER).createLogin();
|
||||
}
|
||||
|
||||
protected Response invalidCredentials(AuthenticationFlowContext context) {
|
||||
|
|
|
@ -425,7 +425,6 @@ public class SAMLEndpoint {
|
|||
@Override
|
||||
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||
byte[] samlBytes = PostBindingUtil.base64Decode(response);
|
||||
String xml = new String(samlBytes);
|
||||
return SAMLRequestParser.parseResponseDocument(samlBytes);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ public class AccountFederatedIdentityBean {
|
|||
this.identities = new LinkedList<FederatedIdentityEntry>(orderedSet);
|
||||
|
||||
// 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);
|
||||
this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(session, realm, user);
|
||||
}
|
||||
|
||||
private FederatedIdentityModel getIdentity(Set<FederatedIdentityModel> identities, String providerId) {
|
||||
|
|
|
@ -222,6 +222,22 @@ public class TokenEndpoint {
|
|||
|
||||
accessCode.setAction(null);
|
||||
UserSessionModel userSession = clientSession.getUserSession();
|
||||
|
||||
if (userSession == null) {
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
throw new ErrorResponseException("invalid_grant", "User session not found", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
UserModel user = userSession.getUser();
|
||||
if (user == null) {
|
||||
event.error(Errors.USER_NOT_FOUND);
|
||||
throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
event.error(Errors.USER_DISABLED);
|
||||
throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
event.user(userSession.getUser());
|
||||
event.session(userSession.getId());
|
||||
|
||||
|
@ -241,17 +257,6 @@ public class TokenEndpoint {
|
|||
throw new ErrorResponseException("invalid_grant", "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
|
||||
if (user == null) {
|
||||
event.error(Errors.USER_NOT_FOUND);
|
||||
throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!user.isEnabled()) {
|
||||
event.error(Errors.USER_DISABLED);
|
||||
throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST);
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.keycloak.services;
|
|||
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
|
|
@ -77,6 +77,12 @@ public class KeycloakSessionServletFilter implements Filter {
|
|||
try {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
} finally {
|
||||
// KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction
|
||||
// should be rolled back
|
||||
if (session.getTransaction() != null && session.getTransaction().isActive()) {
|
||||
session.getTransaction().rollback();
|
||||
}
|
||||
|
||||
session.close();
|
||||
ResteasyProviderFactory.clearContextData();
|
||||
}
|
||||
|
|
|
@ -177,6 +177,8 @@ public abstract class AbstractSecuredLocalService {
|
|||
|
||||
oauth.setClientId(client.getClientId());
|
||||
|
||||
oauth.setSecure(realm.getSslRequired().isRequired(clientConnection));
|
||||
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(getBaseRedirectUri()).path("login-redirect");
|
||||
|
||||
if (path != null) {
|
||||
|
@ -247,8 +249,7 @@ public abstract class AbstractSecuredLocalService {
|
|||
|
||||
URI url = uriBuilder.build();
|
||||
|
||||
// todo httpOnly!
|
||||
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure);
|
||||
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
|
||||
logger.debug("NewCookie: " + cookie.toString());
|
||||
logger.debug("Oauth Redirect to: " + url);
|
||||
return Response.status(302)
|
||||
|
|
|
@ -30,16 +30,20 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.ModelReadOnlyException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
|
@ -298,7 +302,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
@GET
|
||||
public Response passwordPage() {
|
||||
if (auth != null) {
|
||||
account.setPasswordSet(isPasswordSet(auth.getUser()));
|
||||
account.setPasswordSet(isPasswordSet(session, realm, auth.getUser()));
|
||||
}
|
||||
|
||||
return forwardToPage("password", AccountPages.PASSWORD);
|
||||
|
@ -601,7 +605,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
csrfCheck(formData);
|
||||
UserModel user = auth.getUser();
|
||||
|
||||
boolean requireCurrent = isPasswordSet(user);
|
||||
boolean requireCurrent = isPasswordSet(session, realm, user);
|
||||
account.setPasswordSet(requireCurrent);
|
||||
|
||||
String password = formData.getFirst("password");
|
||||
|
@ -723,7 +727,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
if (link != null) {
|
||||
|
||||
// Removing last social provider is not possible if you don't have other possibility to authenticate
|
||||
if (session.users().getFederatedIdentities(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(user)) {
|
||||
if (session.users().getFederatedIdentities(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(session, realm, user)) {
|
||||
session.users().removeFederatedIdentity(realm, user, providerId);
|
||||
|
||||
logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
|
||||
|
@ -758,11 +762,25 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
return Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
|
||||
}
|
||||
|
||||
public static boolean isPasswordSet(UserModel user) {
|
||||
public static boolean isPasswordSet(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
boolean passwordSet = false;
|
||||
|
||||
// See if password is set for user on linked UserFederationProvider
|
||||
if (user.getFederationLink() != null) {
|
||||
passwordSet = true;
|
||||
|
||||
UserFederationProvider federationProvider = null;
|
||||
for (UserFederationProviderModel fedProviderModel : realm.getUserFederationProviders()) {
|
||||
if (fedProviderModel.getId().equals(user.getFederationLink())) {
|
||||
federationProvider = KeycloakModelUtils.getFederationProviderInstance(session, fedProviderModel);
|
||||
}
|
||||
}
|
||||
|
||||
if (federationProvider != null) {
|
||||
Set<String> supportedCreds = federationProvider.getSupportedCredentialTypes(user);
|
||||
if (supportedCreds.contains(UserCredentialModel.PASSWORD)) {
|
||||
passwordSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (UserCredentialValueModel c : user.getCredentialsDirectly()) {
|
||||
|
|
|
@ -581,7 +581,7 @@ public class AuthenticationManagementResource {
|
|||
}
|
||||
|
||||
public List<AuthenticationExecutionModel> getSortedExecutions(AuthenticationFlowModel parentFlow) {
|
||||
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(parentFlow.getId());
|
||||
List<AuthenticationExecutionModel> executions = new LinkedList<>(realm.getAuthenticationExecutions(parentFlow.getId()));
|
||||
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
|
||||
return executions;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.KeyStoreConfig;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
|
@ -225,64 +226,6 @@ public class ClientAttributeCertificateResource {
|
|||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static class KeyStoreConfig {
|
||||
protected Boolean realmCertificate;
|
||||
protected String storePassword;
|
||||
protected String keyPassword;
|
||||
protected String keyAlias;
|
||||
protected String realmAlias;
|
||||
protected String format;
|
||||
|
||||
public Boolean isRealmCertificate() {
|
||||
return realmCertificate;
|
||||
}
|
||||
|
||||
public void setRealmCertificate(Boolean realmCertificate) {
|
||||
this.realmCertificate = realmCertificate;
|
||||
}
|
||||
|
||||
public String getStorePassword() {
|
||||
return storePassword;
|
||||
}
|
||||
|
||||
public void setStorePassword(String storePassword) {
|
||||
this.storePassword = storePassword;
|
||||
}
|
||||
|
||||
public String getKeyPassword() {
|
||||
return keyPassword;
|
||||
}
|
||||
|
||||
public void setKeyPassword(String keyPassword) {
|
||||
this.keyPassword = keyPassword;
|
||||
}
|
||||
|
||||
public String getKeyAlias() {
|
||||
return keyAlias;
|
||||
}
|
||||
|
||||
public void setKeyAlias(String keyAlias) {
|
||||
this.keyAlias = keyAlias;
|
||||
}
|
||||
|
||||
public String getRealmAlias() {
|
||||
return realmAlias;
|
||||
}
|
||||
|
||||
public void setRealmAlias(String realmAlias) {
|
||||
this.realmAlias = realmAlias;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a keystore file for the client, containing private key and public certificate
|
||||
*
|
||||
|
|
|
@ -45,7 +45,6 @@ import org.keycloak.services.managers.RealmManager;
|
|||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
|
@ -62,12 +61,11 @@ import javax.ws.rs.core.MediaType;
|
|||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
|
||||
|
@ -172,54 +170,6 @@ public class ClientResource {
|
|||
return provider.generateInstallation(session, realm, client, keycloak.getBaseUri(uriInfo));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get keycloak.json file
|
||||
*
|
||||
* this method is deprecated, see getInstallationProvider
|
||||
*
|
||||
* Returns a keycloak.json file to be used to configure the adapter of the specified client.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@Deprecated
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("installation/json")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String getInstallation() throws IOException {
|
||||
auth.requireView();
|
||||
|
||||
ClientManager clientManager = new ClientManager(new RealmManager(session));
|
||||
Object rep = clientManager.toInstallationRepresentation(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
|
||||
|
||||
// TODO Temporary solution to pretty-print
|
||||
return JsonSerialization.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get adapter configuration XML for JBoss / Wildfly Keycloak subsystem
|
||||
*
|
||||
* this method is deprecated, see getInstallationProvider
|
||||
*
|
||||
* Returns XML that can be included in the JBoss / Wildfly Keycloak subsystem to configure the adapter of that client.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@Deprecated
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("installation/jboss")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String getJBossInstallation() throws IOException {
|
||||
auth.requireView();
|
||||
|
||||
ClientManager clientManager = new ClientManager(new RealmManager(session));
|
||||
return clientManager.toJBossSubsystemConfig(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the client
|
||||
*
|
||||
|
@ -306,64 +256,6 @@ public class ClientResource {
|
|||
return new RoleContainerResource(uriInfo, realm, auth, client, adminEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed origins
|
||||
*
|
||||
* This is used for CORS requests. Access tokens will have
|
||||
* their allowedOrigins claim set to this value for tokens created for this client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("allowed-origins")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Set<String> getAllowedOrigins()
|
||||
{
|
||||
auth.requireView();
|
||||
return client.getWebOrigins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update allowed origins
|
||||
*
|
||||
* This is used for CORS requests. Access tokens will have
|
||||
* their allowedOrigins claim set to this value for tokens created for this client.
|
||||
*
|
||||
* @param allowedOrigins
|
||||
*/
|
||||
@Path("allowed-origins")
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void updateAllowedOrigins(Set<String> allowedOrigins)
|
||||
{
|
||||
auth.requireManage();
|
||||
|
||||
client.setWebOrigins(allowedOrigins);
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(client).success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified origins from current allowed origins
|
||||
*
|
||||
* This is used for CORS requests. Access tokens will have
|
||||
* their allowedOrigins claim set to this value for tokens created for this client.
|
||||
*
|
||||
* @param allowedOrigins List of origins to delete
|
||||
*/
|
||||
@Path("allowed-origins")
|
||||
@DELETE
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void deleteAllowedOrigins(Set<String> allowedOrigins)
|
||||
{
|
||||
auth.requireManage();
|
||||
|
||||
for (String origin : allowedOrigins) {
|
||||
client.removeWebOrigin(origin);
|
||||
}
|
||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user dedicated to the service account
|
||||
*
|
||||
|
@ -507,41 +399,6 @@ public class ClientResource {
|
|||
return sessions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logout all sessions
|
||||
*
|
||||
* If the client has an admin URL, invalidate all sessions associated with that client directly.
|
||||
*
|
||||
*/
|
||||
@Path("logout-all")
|
||||
@POST
|
||||
public GlobalRequestResult logoutAll() {
|
||||
auth.requireManage();
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
||||
return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the user by username
|
||||
*
|
||||
* If the client has an admin URL, invalidate the sessions for a particular user directly.
|
||||
*
|
||||
*/
|
||||
@Path("logout-user/{username}")
|
||||
@POST
|
||||
public void logout(final @PathParam("username") String username) {
|
||||
auth.requireManage();
|
||||
UserModel user = session.users().getUserByUsername(username, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
||||
new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a cluster node with the client
|
||||
*
|
||||
|
|
|
@ -51,8 +51,14 @@ import org.keycloak.provider.Spi;
|
|||
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||
import org.keycloak.representations.info.*;
|
||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||
import org.keycloak.representations.info.ClientInstallationRepresentation;
|
||||
import org.keycloak.representations.info.MemoryInfoRepresentation;
|
||||
import org.keycloak.representations.info.ProviderRepresentation;
|
||||
import org.keycloak.representations.info.ServerInfoRepresentation;
|
||||
import org.keycloak.representations.info.SpiInfoRepresentation;
|
||||
import org.keycloak.representations.info.SystemInfoRepresentation;
|
||||
import org.keycloak.representations.info.ThemeInfoRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
|
|
@ -61,16 +61,6 @@ public class LocaleHelper {
|
|||
}
|
||||
}
|
||||
|
||||
// User profile
|
||||
if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
|
||||
String localeString = user.getFirstAttribute(UserModel.LOCALE);
|
||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||
if (locale != null) {
|
||||
updateLocaleCookie(session, realm, localeString);
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
// Locale cookie
|
||||
if (httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)) {
|
||||
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
|
||||
|
@ -83,6 +73,16 @@ public class LocaleHelper {
|
|||
}
|
||||
}
|
||||
|
||||
// User profile
|
||||
if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
|
||||
String localeString = user.getFirstAttribute(UserModel.LOCALE);
|
||||
Locale locale = findLocale(realm.getSupportedLocales(), localeString);
|
||||
if (locale != null) {
|
||||
updateLocaleCookie(session, realm, localeString);
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
// ui_locales query parameter
|
||||
if (uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)) {
|
||||
String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.test.broker.saml;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.saml.SAMLRequestParser;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
||||
|
||||
/**
|
||||
* This was failing on IBM JDK
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SAMLParsingTest {
|
||||
|
||||
private static final String SAML_RESPONSE = "PHNhbWxwOkxvZ291dFJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo4MDgxL2F1dGgvcmVhbG1zL3JlYWxtLXdpdGgtYnJva2VyL2Jyb2tlci9rYy1zYW1sLWlkcC1iYXNpYy9lbmRwb2ludCIgSUQ9IklEXzlhMTcxZDIzLWM0MTctNDJmNS05YmNhLWMwOTMxMjNmZDY4YyIgSW5SZXNwb25zZVRvPSJJRF9iYzczMDcxMS0yMDM3LTQzZjMtYWQ3Ni03YmMzMzg0MmZiODciIElzc3VlSW5zdGFudD0iMjAxNi0wMi0yOVQxMjowMDoxNC4wNDRaIiBWZXJzaW9uPSIyLjAiPjxzYW1sOklzc3VlciB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vbG9jYWxob3N0OjgwODIvYXV0aC9yZWFsbXMvcmVhbG0td2l0aC1zYW1sLWlkcC1iYXNpYzwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PC9zYW1scDpMb2dvdXRSZXNwb25zZT4=";
|
||||
|
||||
@Test
|
||||
public void parseTest() throws Exception {
|
||||
byte[] samlBytes = PostBindingUtil.base64Decode(SAML_RESPONSE);
|
||||
SAMLDocumentHolder holder = SAMLRequestParser.parseResponseDocument(samlBytes);
|
||||
Assert.assertNotNull(holder);
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
@ -117,6 +117,68 @@
|
|||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>ssl</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>auth.server.ssl.required</name>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>xml-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>configure-adapter-subsystem-security</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>transform</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformationSets>
|
||||
<transformationSet>
|
||||
<dir>${keycloak.server.home}/standalone/configuration</dir>
|
||||
<includes>
|
||||
<include>standalone.xml</include>
|
||||
</includes>
|
||||
<stylesheet>src/main/xslt/security.xsl</stylesheet>
|
||||
<outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
|
||||
</transformationSet>
|
||||
</transformationSets>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-keystore</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/keystore</directory>
|
||||
<includes>
|
||||
<include>keycloak.jks</include>
|
||||
<include>keycloak.truststore</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jpa</id>
|
||||
<properties>
|
||||
|
@ -255,13 +317,14 @@
|
|||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>ssl</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>auth.server.ssl.required</name>
|
||||
</property>
|
||||
</activation>
|
||||
<id>auth-server-eap7-cluster</id>
|
||||
<properties>
|
||||
<session.cache.owners>1</session.cache.owners>
|
||||
<offline.session.cache.owners>1</offline.session.cache.owners>
|
||||
<login.failure.cache.owners>1</login.failure.cache.owners>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
@ -269,53 +332,62 @@
|
|||
<artifactId>xml-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>configure-adapter-subsystem-security</id>
|
||||
<id>configure-wildfly-datasource</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>transform</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformationSets>
|
||||
<!-- point KeycloakDS datasource to H2 running on TCP port -->
|
||||
<transformationSet>
|
||||
<dir>${keycloak.server.home}/standalone/configuration</dir>
|
||||
<includes>
|
||||
<include>standalone.xml</include>
|
||||
<include>standalone-ha.xml</include>
|
||||
</includes>
|
||||
<stylesheet>src/main/xslt/security.xsl</stylesheet>
|
||||
<stylesheet>src/main/xslt/datasource-jdbc-url.xsl</stylesheet>
|
||||
<outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
|
||||
<parameters>
|
||||
<parameter>
|
||||
<name>pool.name</name>
|
||||
<value>KeycloakDS</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>jdbc.url</name>
|
||||
<value>jdbc:h2:tcp://${jboss.bind.address:localhost}:9092/mem:keycloak;DB_CLOSE_DELAY=-1</value>
|
||||
</parameter>
|
||||
</parameters>
|
||||
</transformationSet>
|
||||
<transformationSet>
|
||||
<dir>${keycloak.server.home}/standalone/configuration</dir>
|
||||
<includes>
|
||||
<include>standalone-ha.xml</include>
|
||||
</includes>
|
||||
<stylesheet>src/main/xslt/ispn-cache-owners.xsl</stylesheet>
|
||||
<outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
|
||||
<parameters>
|
||||
<parameter>
|
||||
<name>sessionCacheOwners</name>
|
||||
<value>${session.cache.owners}</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>offlineSessionCacheOwners</name>
|
||||
<value>${offline.session.cache.owners}</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>loginFailureCacheOwners</name>
|
||||
<value>${login.failure.cache.owners}</value>
|
||||
</parameter>
|
||||
</parameters>
|
||||
</transformationSet>
|
||||
</transformationSets>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-keystore</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/keystore</directory>
|
||||
<includes>
|
||||
<include>keycloak.jks</include>
|
||||
<include>keycloak.truststore</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:xalan="http://xml.apache.org/xalan"
|
||||
xmlns:j="urn:jboss:domain:4.0"
|
||||
xmlns:ds="urn:jboss:domain:datasources:4.0"
|
||||
xmlns:k="urn:jboss:domain:keycloak:1.1"
|
||||
xmlns:sec="urn:jboss:domain:security:1.2"
|
||||
version="2.0"
|
||||
exclude-result-prefixes="xalan j ds k sec">
|
||||
|
||||
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||
<xsl:strip-space elements="*"/>
|
||||
|
||||
|
||||
<xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
|
||||
|
||||
<xsl:param name="pool.name" select="'KeycloakDS'"/>
|
||||
<xsl:param name="jdbc.url" />
|
||||
|
||||
<!-- replace JDBC URL -->
|
||||
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
|
||||
/*[local-name()='datasources' and starts-with(namespace-uri(), $nsDS)]
|
||||
/*[local-name()='datasource' and starts-with(namespace-uri(), $nsDS) and @pool-name=$pool.name]
|
||||
/*[local-name()='connection-url' and starts-with(namespace-uri(), $nsDS)]">
|
||||
<connection-url>
|
||||
<xsl:value-of select="$jdbc.url"/>
|
||||
</connection-url>
|
||||
</xsl:template>
|
||||
|
||||
<!-- Copy everything else. -->
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()" />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
|
@ -0,0 +1,40 @@
|
|||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:xalan="http://xml.apache.org/xalan"
|
||||
xmlns:j="urn:jboss:domain:4.0"
|
||||
xmlns:i="urn:jboss:domain:infinispan:4.0"
|
||||
version="2.0"
|
||||
exclude-result-prefixes="xalan i">
|
||||
|
||||
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
|
||||
<xsl:strip-space elements="*"/>
|
||||
|
||||
<xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
|
||||
|
||||
<xsl:param name="sessionCacheOwners" select="'1'"/>
|
||||
<xsl:param name="offlineSessionCacheOwners" select="'1'"/>
|
||||
<xsl:param name="loginFailureCacheOwners" select="'1'"/>
|
||||
|
||||
<xsl:template match="//i:cache-container/i:distributed-cache[@name='sessions']/@owners">
|
||||
<xsl:attribute name="owners">
|
||||
<xsl:value-of select="$sessionCacheOwners"/>
|
||||
</xsl:attribute>
|
||||
</xsl:template>
|
||||
<xsl:template match="//i:cache-container/i:distributed-cache[@name='offlineSessions']/@owners">
|
||||
<xsl:attribute name="owners">
|
||||
<xsl:value-of select="$offlineSessionCacheOwners"/>
|
||||
</xsl:attribute>
|
||||
</xsl:template>
|
||||
<xsl:template match="//i:cache-container/i:distributed-cache[@name='loginFailures']/@owners">
|
||||
<xsl:attribute name="owners">
|
||||
<xsl:value-of select="$loginFailureCacheOwners"/>
|
||||
</xsl:attribute>
|
||||
</xsl:template>
|
||||
|
||||
<!-- Copy everything else. -->
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()" />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
|
@ -54,6 +54,13 @@
|
|||
<module>eap7</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>auth-server-eap7-cluster</id>
|
||||
<modules>
|
||||
<module>eap7</module>
|
||||
<module>wildfly-balancer</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -30,6 +30,8 @@ import java.net.URI;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.Response.StatusType;
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
|
@ -44,6 +46,11 @@ public class ApiUtil {
|
|||
|
||||
public static String getCreatedId(Response response) {
|
||||
URI location = response.getLocation();
|
||||
if (!response.getStatusInfo().equals(Status.CREATED)) {
|
||||
StatusType statusInfo = response.getStatusInfo();
|
||||
throw new RuntimeException("Create method returned status " +
|
||||
statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)");
|
||||
}
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ package org.keycloak.testsuite.auth.page.account;
|
|||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import org.jboss.arquillian.graphene.findby.FindByJQuery;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
import org.keycloak.testsuite.page.PageWithLogOutAction;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy;
|
|||
* @author <a href="mailto:pmensik@redhat.com">Petr Mensik</a>
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class AccountManagement extends AuthRealm {
|
||||
public class AccountManagement extends AuthRealm implements PageWithLogOutAction {
|
||||
|
||||
@Override
|
||||
public UriBuilder createUriBuilder() {
|
||||
|
@ -77,6 +77,11 @@ public class AccountManagement extends AuthRealm {
|
|||
signOutLink.click();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
signOut();
|
||||
}
|
||||
|
||||
public void account() {
|
||||
accountLink.click();
|
||||
}
|
||||
|
@ -108,4 +113,5 @@ public class AccountManagement extends AuthRealm {
|
|||
public void waitForAccountLinkPresent() {
|
||||
waitUntilElement(accountLink, "account link should be present").is().present();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
|
|||
import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
|
||||
import org.keycloak.testsuite.console.page.fragment.Menu;
|
||||
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
|
||||
import org.keycloak.testsuite.page.PageWithLogOutAction;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
|
@ -32,7 +33,7 @@ import org.openqa.selenium.support.FindBy;
|
|||
*
|
||||
* @author Petr Mensik
|
||||
*/
|
||||
public class AdminConsole extends AuthServer implements PageWithLoginUrl {
|
||||
public class AdminConsole extends AuthServer implements PageWithLoginUrl, PageWithLogOutAction {
|
||||
|
||||
public static final String ADMIN_REALM = "adminRealm";
|
||||
|
||||
|
@ -80,6 +81,7 @@ public class AdminConsole extends AuthServer implements PageWithLoginUrl {
|
|||
@FindBy(css = "navbar-brand")
|
||||
protected WebElement brandLink;
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
menu.logOut();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package org.keycloak.testsuite.page;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public interface PageWithLogOutAction {
|
||||
|
||||
public void logOut();
|
||||
|
||||
}
|
|
@ -17,10 +17,13 @@
|
|||
package org.keycloak.testsuite.account;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.testsuite.auth.page.login.Registration;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -58,6 +61,7 @@ public class RegistrationTest extends AbstractAccountManagementTest {
|
|||
setPasswordFor(newUser, PASSWORD);
|
||||
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
assertTrue("Registration should be allowed.", testRealmResource().toRepresentation().isRegistrationAllowed());
|
||||
testRealmLoginPage.form().register();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.account;
|
|||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.AfterClass;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -61,6 +62,7 @@ public class ResetCredentialsTest extends AbstractAccountManagementTest {
|
|||
}
|
||||
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
assertTrue("Reset password should be allowed.", testRealmResource().toRepresentation().isResetPasswordAllowed());
|
||||
testRealmLoginPage.form().forgotPassword();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,22 +23,29 @@ import org.jboss.arquillian.drone.api.annotation.Drone;
|
|||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.After;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.adapter.page.SessionPortal;
|
||||
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
||||
|
||||
import org.keycloak.testsuite.auth.page.account.Sessions;
|
||||
import org.keycloak.testsuite.auth.page.login.Login;
|
||||
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
|
||||
|
||||
import org.keycloak.testsuite.util.SecondBrowser;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
@ -156,9 +163,11 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
|
|||
public void testAdminApplicationLogout() {
|
||||
// login as bburke
|
||||
loginAndCheckSession(driver, testRealmLoginPage);
|
||||
|
||||
// logout mposolda with admin client
|
||||
findClientResourceByClientId(testRealmResource(), "session-portal")
|
||||
.logoutUser("mposolda");
|
||||
UserRepresentation mposolda = testRealmResource().users().search("mposolda", null, null, null, null, null).get(0);
|
||||
testRealmResource().users().get(mposolda.getId()).logout();
|
||||
|
||||
// bburke should be still logged with original httpSession in our browser window
|
||||
sessionPortalPage.navigateTo();
|
||||
assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString());
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
||||
|
@ -92,14 +93,41 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices());
|
||||
}
|
||||
|
||||
void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) {
|
||||
Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias());
|
||||
Assert.assertEquals("Execution authenticator - " + actual.getAuthenticator(), expected.getAuthenticator(), actual.getAuthenticator());
|
||||
Assert.assertEquals("Execution userSetupAllowed - " + actual.getAuthenticator(), expected.isUserSetupAllowed(), actual.isUserSetupAllowed());
|
||||
Assert.assertEquals("Execution authenticatorFlow - " + actual.getAuthenticator(), expected.isAutheticatorFlow(), actual.isAutheticatorFlow());
|
||||
Assert.assertEquals("Execution authenticatorConfig - " + actual.getAuthenticator(), expected.getAuthenticatorConfig(), actual.getAuthenticatorConfig());
|
||||
Assert.assertEquals("Execution priority - " + actual.getAuthenticator(), expected.getPriority(), actual.getPriority());
|
||||
Assert.assertEquals("Execution requirement - " + actual.getAuthenticator(), expected.getRequirement(), actual.getRequirement());
|
||||
}
|
||||
|
||||
void compareExecutions(List<AuthenticationExecutionExportRepresentation> expected, List<AuthenticationExecutionExportRepresentation> actual) {
|
||||
Assert.assertNotNull("Executions should not be null", actual);
|
||||
Assert.assertEquals("Size", expected.size(), actual.size());
|
||||
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
compareExecution(expected.get(i), actual.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
void compareFlows(AuthenticationFlowRepresentation expected, AuthenticationFlowRepresentation actual) {
|
||||
Assert.assertEquals("Flow alias", expected.getAlias(), actual.getAlias());
|
||||
Assert.assertEquals("Flow description", expected.getDescription(), actual.getDescription());
|
||||
Assert.assertEquals("Flow providerId", expected.getProviderId(), actual.getProviderId());
|
||||
Assert.assertEquals("Flow top level", expected.isTopLevel(), actual.isTopLevel());
|
||||
Assert.assertEquals("Flow built-in", expected.isBuiltIn(), actual.isBuiltIn());
|
||||
}
|
||||
|
||||
List<AuthenticationExecutionExportRepresentation> expectedExecs = expected.getAuthenticationExecutions();
|
||||
List<AuthenticationExecutionExportRepresentation> actualExecs = actual.getAuthenticationExecutions();
|
||||
|
||||
if (expectedExecs == null) {
|
||||
Assert.assertTrue("Executions should be null or empty", actualExecs == null || actualExecs.size() == 0);
|
||||
} else {
|
||||
compareExecutions(expectedExecs, actualExecs);
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticationFlowRepresentation newFlow(String alias, String description,
|
||||
String providerId, boolean topLevel, boolean builtIn) {
|
||||
|
@ -112,8 +140,8 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
|
|||
return flow;
|
||||
}
|
||||
|
||||
AuthenticationExecutionInfoRepresentation newExecution(String displayName, String providerId, Boolean configurable,
|
||||
int level, int index, String requirement, Boolean authFlow, String[] choices) {
|
||||
AuthenticationExecutionInfoRepresentation newExecInfo(String displayName, String providerId, Boolean configurable,
|
||||
int level, int index, String requirement, Boolean authFlow, String[] choices) {
|
||||
|
||||
AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
|
||||
execution.setRequirement(requirement);
|
||||
|
@ -129,6 +157,12 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
|
|||
return execution;
|
||||
}
|
||||
|
||||
void addExecInfo(List<AuthenticationExecutionInfoRepresentation> target, String displayName, String providerId, Boolean configurable,
|
||||
int level, int index, String requirement, Boolean authFlow, String[] choices) {
|
||||
|
||||
AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices);
|
||||
target.add(exec);
|
||||
}
|
||||
|
||||
AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) {
|
||||
AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
|
||||
|
|
|
@ -83,7 +83,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
|
|||
response.close();
|
||||
}
|
||||
|
||||
compareExecution(newExecution("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
|
||||
compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
|
||||
|
||||
// remove execution
|
||||
authMgmtResource.removeExecution(exec.getId());
|
||||
|
@ -143,7 +143,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
|
|||
|
||||
// Note: there is no checking in addExecution if requirement is one of requirementChoices
|
||||
// Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
|
||||
compareExecution(newExecution("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
|
||||
compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -19,12 +19,15 @@ package org.keycloak.testsuite.admin.authentication;
|
|||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
|
@ -62,13 +65,52 @@ public class FlowTest extends AbstractAuthenticationTest {
|
|||
response.close();
|
||||
}
|
||||
|
||||
// check that new flow is returned
|
||||
// check that new flow is returned in a children list
|
||||
flows = authMgmtResource.getFlows();
|
||||
AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows);
|
||||
|
||||
Assert.assertNotNull("created flow visible", found);
|
||||
Assert.assertNotNull("created flow visible in parent", found);
|
||||
compareFlows(newFlow, found);
|
||||
|
||||
// check that new flow is returned individually
|
||||
AuthenticationFlowRepresentation found2 = authMgmtResource.getFlow(found.getId());
|
||||
Assert.assertNotNull("created flow visible directly", found2);
|
||||
compareFlows(newFlow, found2);
|
||||
|
||||
|
||||
// add execution flow using a different method
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("alias", "SomeFlow");
|
||||
data.put("type", "basic-flow");
|
||||
data.put("description", "Test flow");
|
||||
data.put("provider", "registration-page-form");
|
||||
|
||||
try {
|
||||
authMgmtResource.addExecutionFlow("inexistent-parent-flow-alias", data);
|
||||
Assert.fail("addExecutionFlow for inexistent parent should have failed");
|
||||
} catch (Exception expected) {
|
||||
}
|
||||
|
||||
authMgmtResource.addExecutionFlow("browser-2", data);
|
||||
|
||||
// check that new flow is returned in a children list
|
||||
flows = authMgmtResource.getFlows();
|
||||
found2 = findFlowByAlias("browser-2", flows);
|
||||
Assert.assertNotNull("created flow visible in parent", found2);
|
||||
|
||||
List<AuthenticationExecutionExportRepresentation> execs = found2.getAuthenticationExecutions();
|
||||
Assert.assertNotNull(execs);
|
||||
Assert.assertEquals("Size one", 1, execs.size());
|
||||
|
||||
AuthenticationExecutionExportRepresentation expected = new AuthenticationExecutionExportRepresentation();
|
||||
expected.setFlowAlias("SomeFlow");
|
||||
expected.setUserSetupAllowed(false);
|
||||
expected.setAuthenticator("registration-page-form");
|
||||
expected.setAutheticatorFlow(true);
|
||||
expected.setRequirement("DISABLED");
|
||||
expected.setPriority(0);
|
||||
compareExecution(expected, execs.get(0));
|
||||
|
||||
// delete non-built-in flow
|
||||
authMgmtResource.deleteFlow(found.getId());
|
||||
|
||||
|
@ -122,7 +164,31 @@ public class FlowTest extends AbstractAuthenticationTest {
|
|||
// adjust expected values before comparing
|
||||
browser.setAlias("Copy of browser");
|
||||
browser.setBuiltIn(false);
|
||||
browser.getAuthenticationExecutions().get(2).setFlowAlias("Copy of browser forms");
|
||||
compareFlows(browser, copyOfBrowser);
|
||||
|
||||
// get new flow directly and compare
|
||||
copyOfBrowser = authMgmtResource.getFlow(copyOfBrowser.getId());
|
||||
Assert.assertNotNull(copyOfBrowser);
|
||||
compareFlows(browser, copyOfBrowser);
|
||||
}
|
||||
|
||||
@Test
|
||||
// KEYCLOAK-2580
|
||||
public void addExecutionFlow() {
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
params.put("newName", "parent");
|
||||
Response response = authMgmtResource.copy("browser", params);
|
||||
Assert.assertEquals(201, response.getStatus());
|
||||
response.close();
|
||||
|
||||
params = new HashMap<>();
|
||||
params.put("alias", "child");
|
||||
params.put("description", "Description");
|
||||
params.put("provider", "registration-page-form");
|
||||
params.put("type", "basic-flow");
|
||||
|
||||
authMgmtResource.addExecutionFlow("parent", params);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication;
|
|||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
||||
|
@ -81,12 +82,12 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
|
|||
FlowExecutions fe2 = it2.next();
|
||||
|
||||
compareFlows(fe1.flow, fe2.flow);
|
||||
compareExecutions(fe1.executions, fe2.executions);
|
||||
compareExecutionsInfo(fe1.executions, fe2.executions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void compareExecutions(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
|
||||
private void compareExecutionsInfo(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
|
||||
Assert.assertEquals("Executions count", expected.size(), actual.size());
|
||||
Iterator<AuthenticationExecutionInfoRepresentation> it1 = expected.iterator();
|
||||
Iterator<AuthenticationExecutionInfoRepresentation> it2 = actual.iterator();
|
||||
|
@ -124,66 +125,117 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
|
|||
LinkedList<FlowExecutions> expected = new LinkedList<>();
|
||||
|
||||
AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
|
||||
List<AuthenticationExecutionInfoRepresentation> executions = new LinkedList<>();
|
||||
executions.add(newExecution("Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
|
||||
executions.add(newExecution("Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}));
|
||||
executions.add(newExecution("OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10);
|
||||
addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20);
|
||||
addExecExport(flow, "forms", false, null, true, null, ALTERNATIVE, 30);
|
||||
|
||||
List<AuthenticationExecutionInfoRepresentation> execs = new LinkedList<>();
|
||||
addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED});
|
||||
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("clients", "Base authentication for clients", "client-flow", true, true);
|
||||
executions = new LinkedList<>();
|
||||
executions.add(newExecution("Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
|
||||
executions.add(newExecution("Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, null, false, "client-secret", false, null, ALTERNATIVE, 10);
|
||||
addExecExport(flow, null, false, "client-jwt", false, null, ALTERNATIVE, 20);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
|
||||
executions = new LinkedList<>();
|
||||
executions.add(newExecution("Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
|
||||
executions.add(newExecution("Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, null, false, "direct-grant-validate-username", false, null, REQUIRED, 10);
|
||||
addExecExport(flow, null, false, "direct-grant-validate-password", false, null, REQUIRED, 20);
|
||||
addExecExport(flow, null, false, "direct-grant-validate-otp", false, null, OPTIONAL, 30);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, "Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
|
||||
addExecInfo(execs, "Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("first broker login", "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
|
||||
"basic-flow", true, true);
|
||||
executions = new LinkedList<>();
|
||||
executions.add(newExecution("Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED}));
|
||||
executions.add(newExecution("OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, null, false, "idp-review-profile", false, "review profile config", REQUIRED, 10);
|
||||
addExecExport(flow, null, false, "idp-create-user-if-unique", false, "create unique user config", ALTERNATIVE, 20);
|
||||
addExecExport(flow, "Handle Existing Account", false, null, true, null, ALTERNATIVE, 30);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, "Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED});
|
||||
addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("registration", "registration flow", "basic-flow", true, true);
|
||||
executions = new LinkedList<>();
|
||||
executions.add(newExecution("registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
|
||||
executions.add(newExecution("Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, "registration form", false, "registration-page-form", true, null, REQUIRED, 10);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, "registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED});
|
||||
addExecInfo(execs, "Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("reset credentials", "Reset credentials for a user if they forgot their password or something", "basic-flow", true, true);
|
||||
executions = new LinkedList<>();
|
||||
executions.add(newExecution("Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
|
||||
executions.add(newExecution("Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED}));
|
||||
executions.add(newExecution("Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
|
||||
executions.add(newExecution("Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, null, false, "reset-credentials-choose-user", false, null, REQUIRED, 10);
|
||||
addExecExport(flow, null, false, "reset-credential-email", false, null, REQUIRED, 20);
|
||||
addExecExport(flow, null, false, "reset-password", false, null, REQUIRED, 30);
|
||||
addExecExport(flow, null, false, "reset-otp", false, null, OPTIONAL, 40);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, "Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
|
||||
addExecInfo(execs, "Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED});
|
||||
addExecInfo(execs, "Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
|
||||
addExecInfo(execs, "Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
flow = newFlow("saml ecp", "SAML ECP Profile Authentication Flow", "basic-flow", true, true);
|
||||
executions = new LinkedList<>();
|
||||
executions.add(newExecution(null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{}));
|
||||
expected.add(new FlowExecutions(flow, executions));
|
||||
addExecExport(flow, null, false, "http-basic-authenticator", false, null, REQUIRED, 10);
|
||||
|
||||
execs = new LinkedList<>();
|
||||
addExecInfo(execs, null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{});
|
||||
expected.add(new FlowExecutions(flow, execs));
|
||||
|
||||
return expected;
|
||||
}
|
||||
|
||||
static class FlowExecutions implements Comparable<FlowExecutions> {
|
||||
private void addExecExport(AuthenticationFlowRepresentation flow, String flowAlias, boolean userSetupAllowed,
|
||||
String authenticator, boolean authenticatorFlow, String authenticatorConfig,
|
||||
String requirement, int priority) {
|
||||
|
||||
AuthenticationExecutionExportRepresentation rep = newExecutionExportRepresentation(flowAlias, userSetupAllowed,
|
||||
authenticator, authenticatorFlow, authenticatorConfig, requirement, priority);
|
||||
|
||||
List<AuthenticationExecutionExportRepresentation> execs = flow.getAuthenticationExecutions();
|
||||
if (execs == null) {
|
||||
execs = new ArrayList<>();
|
||||
flow.setAuthenticationExecutions(execs);
|
||||
}
|
||||
execs.add(rep);
|
||||
}
|
||||
|
||||
private AuthenticationExecutionExportRepresentation newExecutionExportRepresentation(String flowAlias, boolean userSetupAllowed, String authenticator, boolean authenticatorFlow, String authenticatorConfig, String requirement, int priority) {
|
||||
AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation();
|
||||
rep.setFlowAlias(flowAlias);
|
||||
rep.setUserSetupAllowed(userSetupAllowed);
|
||||
rep.setAuthenticator(authenticator);
|
||||
rep.setAutheticatorFlow(authenticatorFlow);
|
||||
rep.setAuthenticatorConfig(authenticatorConfig);
|
||||
rep.setRequirement(requirement);
|
||||
rep.setPriority(priority);
|
||||
return rep;
|
||||
}
|
||||
|
||||
private static class FlowExecutions implements Comparable<FlowExecutions> {
|
||||
AuthenticationFlowRepresentation flow;
|
||||
List<AuthenticationExecutionInfoRepresentation> executions;
|
||||
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.testsuite.admin.authentication;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class InitialProvidersTest extends AbstractAuthenticationTest {
|
||||
|
||||
@Test
|
||||
public void testAuthenticationProvidersList() {
|
||||
|
||||
List<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
|
||||
providers = sortProviders(providers);
|
||||
|
||||
compareProviders(expectedAuthProviders(), providers);
|
||||
}
|
||||
|
||||
private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
|
||||
|
||||
Assert.assertEquals("Providers count", expected.size(), actual.size());
|
||||
|
||||
Iterator<Map<String, Object>> it1 = expected.iterator();
|
||||
Iterator<Map<String, Object>> it2 = actual.iterator();
|
||||
|
||||
while (it1.hasNext()) {
|
||||
Assert.assertEquals("Provider", it1.next(), it2.next());
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> expectedAuthProviders() {
|
||||
ArrayList<Map<String, Object>> result = new ArrayList<>();
|
||||
result.add(newClientProvider("auth-conditional-otp-form", "Conditional OTP Form", "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions."));
|
||||
result.add(newClientProvider("auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server."));
|
||||
result.add(newClientProvider("auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form."));
|
||||
result.add(newClientProvider("auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos."));
|
||||
result.add(newClientProvider("auth-username-password-form", "Username Password Form", "Validates a username and password from login form."));
|
||||
result.add(newClientProvider("direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request"));
|
||||
result.add(newClientProvider("direct-grant-validate-password", "Password", "Validates the password supplied as a 'password' form parameter in direct grant request"));
|
||||
result.add(newClientProvider("direct-grant-validate-username", "Username Validation", "Validates the username supplied as a 'username' form parameter in direct grant request"));
|
||||
result.add(newClientProvider("http-basic-authenticator", null, null));
|
||||
result.add(newClientProvider("idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict"));
|
||||
result.add(newClientProvider("idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user"));
|
||||
result.add(newClientProvider("idp-email-verification", "Verify existing account by Email", "Email verification of existing Keycloak user, that wants to link his user account with identity provider"));
|
||||
result.add(newClientProvider("idp-review-profile", "Review Profile", "User reviews and updates profile data retrieved from Identity Provider in the displayed form"));
|
||||
result.add(newClientProvider("idp-username-password-form", "Username Password Form for identity provider reauthentication", "Validates a password from login form. Username is already known from identity provider authentication"));
|
||||
result.add(newClientProvider("reset-credential-email", "Send Reset Email", "Send email to user and wait for response."));
|
||||
result.add(newClientProvider("reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for"));
|
||||
result.add(newClientProvider("reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED. Will also set it if execution is OPTIONAL and the OTP is currently configured for it."));
|
||||
result.add(newClientProvider("reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. Will also set it if execution is OPTIONAL and the password is currently configured for it."));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> newClientProvider(String id, String displayName, String description) {
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("id", id);
|
||||
obj.put("displayName", displayName);
|
||||
obj.put("description", description);
|
||||
return obj;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> sortProviders(List<Map<String, Object>> providers) {
|
||||
ArrayList<Map<String, Object>> sorted = new ArrayList<>(providers);
|
||||
Collections.sort(sorted, new ProviderComparator());
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static class ProviderComparator implements Comparator<Map<String, Object>> {
|
||||
@Override
|
||||
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
|
||||
return String.valueOf(o1.get("id")).compareTo(String.valueOf(o2.get("id")));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.testsuite.admin.authentication;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class ProvidersTest extends AbstractAuthenticationTest {
|
||||
|
||||
@Test
|
||||
public void testFormProviders() {
|
||||
List<Map<String, Object>> result = authMgmtResource.getFormProviders();
|
||||
|
||||
Assert.assertNotNull("null result", result);
|
||||
Assert.assertEquals("size", 1, result.size());
|
||||
Map<String, Object> item = result.get(0);
|
||||
|
||||
Assert.assertEquals("id", "registration-page-form", item.get("id"));
|
||||
Assert.assertEquals("displayName", "Registration Page", item.get("displayName"));
|
||||
Assert.assertEquals("description", "This is the controller for the registration page", item.get("description"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormActionProviders() {
|
||||
List<Map<String, Object>> result = authMgmtResource.getFormActionProviders();
|
||||
|
||||
List<Map<String, Object>> expected = new LinkedList<>();
|
||||
addProviderInfo(expected, "registration-profile-action", "Profile Validation",
|
||||
"Validates email, first name, and last name attributes and stores them in user data.");
|
||||
addProviderInfo(expected, "registration-recaptcha-action", "Recaptcha",
|
||||
"Adds Google Recaptcha button. Recaptchas verify that the entity that is registering is a human. " +
|
||||
"This can only be used on the internet and must be configured after you add it.");
|
||||
addProviderInfo(expected, "registration-password-action", "Password Validation",
|
||||
"Validates that password matches password confirmation field. It also will store password in user's credential store.");
|
||||
addProviderInfo(expected, "registration-user-creation", "Registration User Creation",
|
||||
"This action must always be first! Validates the username of the user in validation phase. " +
|
||||
"In success phase, this will create the user in the database.");
|
||||
|
||||
compareProviders(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientAuthenticatorProviders() {
|
||||
List<Map<String, Object>> result = authMgmtResource.getClientAuthenticatorProviders();
|
||||
|
||||
List<Map<String, Object>> expected = new LinkedList<>();
|
||||
addProviderInfo(expected, "client-jwt", "Signed Jwt",
|
||||
"Validates client based on signed JWT issued by client and signed with the Client private key");
|
||||
addProviderInfo(expected, "client-secret", "Client Id and Secret", "Validates client based on 'client_id' and " +
|
||||
"'client_secret' sent either in request parameters or in 'Authorization: Basic' header");
|
||||
|
||||
compareProviders(expected, result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInitialAuthenticationProviders() {
|
||||
|
||||
List<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
|
||||
providers = sortProviders(providers);
|
||||
|
||||
compareProviders(expectedAuthProviders(), providers);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> expectedAuthProviders() {
|
||||
ArrayList<Map<String, Object>> result = new ArrayList<>();
|
||||
addProviderInfo(result, "auth-conditional-otp-form", "Conditional OTP Form",
|
||||
"Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions.");
|
||||
addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server.");
|
||||
addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form.");
|
||||
addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos.");
|
||||
addProviderInfo(result, "auth-username-password-form", "Username Password Form",
|
||||
"Validates a username and password from login form.");
|
||||
addProviderInfo(result, "direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request");
|
||||
addProviderInfo(result, "direct-grant-validate-password", "Password",
|
||||
"Validates the password supplied as a 'password' form parameter in direct grant request");
|
||||
addProviderInfo(result, "direct-grant-validate-username", "Username Validation",
|
||||
"Validates the username supplied as a 'username' form parameter in direct grant request");
|
||||
addProviderInfo(result, "http-basic-authenticator", null, null);
|
||||
addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " +
|
||||
"to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict");
|
||||
addProviderInfo(result, "idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account " +
|
||||
"with same email like identity provider. If no, create new user");
|
||||
addProviderInfo(result, "idp-email-verification", "Verify existing account by Email", "Email verification of existing Keycloak " +
|
||||
"user, that wants to link his user account with identity provider");
|
||||
addProviderInfo(result, "idp-review-profile", "Review Profile",
|
||||
"User reviews and updates profile data retrieved from Identity Provider in the displayed form");
|
||||
addProviderInfo(result, "idp-username-password-form", "Username Password Form for identity provider reauthentication",
|
||||
"Validates a password from login form. Username is already known from identity provider authentication");
|
||||
addProviderInfo(result, "reset-credential-email", "Send Reset Email", "Send email to user and wait for response.");
|
||||
addProviderInfo(result, "reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for");
|
||||
addProviderInfo(result, "reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED. " +
|
||||
"Will also set it if execution is OPTIONAL and the OTP is currently configured for it.");
|
||||
addProviderInfo(result, "reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. " +
|
||||
"Will also set it if execution is OPTIONAL and the password is currently configured for it.");
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> sortProviders(List<Map<String, Object>> providers) {
|
||||
ArrayList<Map<String, Object>> sorted = new ArrayList<>(providers);
|
||||
Collections.sort(sorted, new ProviderComparator());
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
|
||||
Assert.assertEquals("Providers count", expected.size(), actual.size());
|
||||
// compare ignoring list and map impl types
|
||||
Assert.assertEquals(normalizeResults(expected), normalizeResults(actual));
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> normalizeResults(List<Map<String, Object>> list) {
|
||||
ArrayList<Map<String, Object>> result = new ArrayList();
|
||||
for (Map<String, Object> item: list) {
|
||||
result.add(new HashMap(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addProviderInfo(List<Map<String, Object>> list, String id, String displayName, String description) {
|
||||
HashMap<String, Object> item = new HashMap<>();
|
||||
item.put("id", id);
|
||||
item.put("displayName", displayName);
|
||||
item.put("description", description);
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
private static class ProviderComparator implements Comparator<Map<String, Object>> {
|
||||
@Override
|
||||
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
|
||||
return String.valueOf(o1.get("id")).compareTo(String.valueOf(o2.get("id")));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.testsuite.admin.authentication;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class RequiredActionsTest extends AbstractAuthenticationTest {
|
||||
|
||||
@Test
|
||||
public void testRequiredActions() {
|
||||
List<RequiredActionProviderRepresentation> result = authMgmtResource.getRequiredActions();
|
||||
|
||||
List<RequiredActionProviderRepresentation> expected = new ArrayList<>();
|
||||
addRequiredAction(expected, "CONFIGURE_TOTP", "Configure Totp", true, false, null);
|
||||
addRequiredAction(expected, "UPDATE_PASSWORD", "Update Password", true, false, null);
|
||||
addRequiredAction(expected, "UPDATE_PROFILE", "Update Profile", true, false, null);
|
||||
addRequiredAction(expected, "VERIFY_EMAIL", "Verify Email", true, false, null);
|
||||
addRequiredAction(expected, "terms_and_conditions", "Terms and Conditions", false, false, null);
|
||||
|
||||
compareRequiredActions(expected, sort(result));
|
||||
|
||||
RequiredActionProviderRepresentation forUpdate = newRequiredAction("VERIFY_EMAIL", "Verify Email", false, false, null);
|
||||
try {
|
||||
authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate);
|
||||
Assert.fail("updateRequiredAction should fail due to null config");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
forUpdate.setConfig(Collections.<String, String>emptyMap());
|
||||
authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate);
|
||||
|
||||
result = authMgmtResource.getRequiredActions();
|
||||
RequiredActionProviderRepresentation updated = findRequiredActionByAlias(forUpdate.getAlias(), result);
|
||||
|
||||
Assert.assertNotNull("Required Action still there", updated);
|
||||
compareRequiredAction(forUpdate, updated);
|
||||
}
|
||||
|
||||
|
||||
private RequiredActionProviderRepresentation findRequiredActionByAlias(String alias, List<RequiredActionProviderRepresentation> list) {
|
||||
for (RequiredActionProviderRepresentation a: list) {
|
||||
if (alias.equals(a.getAlias())) {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<RequiredActionProviderRepresentation> sort(List<RequiredActionProviderRepresentation> list) {
|
||||
ArrayList<RequiredActionProviderRepresentation> sorted = new ArrayList<>(list);
|
||||
Collections.sort(sorted, new RequiredActionProviderComparator());
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private void compareRequiredActions(List<RequiredActionProviderRepresentation> expected, List<RequiredActionProviderRepresentation> actual) {
|
||||
Assert.assertNotNull("Actual null", actual);
|
||||
Assert.assertEquals("Required actions count", expected.size(), actual.size());
|
||||
|
||||
Iterator<RequiredActionProviderRepresentation> ite = expected.iterator();
|
||||
Iterator<RequiredActionProviderRepresentation> ita = actual.iterator();
|
||||
while (ite.hasNext()) {
|
||||
compareRequiredAction(ite.next(), ita.next());
|
||||
}
|
||||
}
|
||||
|
||||
private void compareRequiredAction(RequiredActionProviderRepresentation expected, RequiredActionProviderRepresentation actual) {
|
||||
Assert.assertEquals("alias - " + expected.getAlias(), expected.getAlias(), actual.getAlias());
|
||||
Assert.assertEquals("name - " + expected.getAlias(), expected.getName(), actual.getName());
|
||||
Assert.assertEquals("enabled - " + expected.getAlias(), expected.isEnabled(), actual.isEnabled());
|
||||
Assert.assertEquals("defaultAction - " + expected.getAlias(), expected.isDefaultAction(), actual.isDefaultAction());
|
||||
Assert.assertEquals("config - " + expected.getAlias(), expected.getConfig() != null ? expected.getConfig() : Collections.emptyMap(), actual.getConfig());
|
||||
}
|
||||
|
||||
private void addRequiredAction(List<RequiredActionProviderRepresentation> target, String alias, String name, boolean enabled, boolean defaultAction, Map conf) {
|
||||
target.add(newRequiredAction(alias, name, enabled, defaultAction, conf));
|
||||
}
|
||||
|
||||
private RequiredActionProviderRepresentation newRequiredAction(String alias, String name, boolean enabled, boolean defaultAction, Map conf) {
|
||||
RequiredActionProviderRepresentation action = new RequiredActionProviderRepresentation();
|
||||
action.setAlias(alias);
|
||||
action.setName(name);
|
||||
action.setEnabled(enabled);
|
||||
action.setDefaultAction(defaultAction);
|
||||
action.setConfig(conf);
|
||||
return action;
|
||||
}
|
||||
|
||||
private static class RequiredActionProviderComparator implements Comparator<RequiredActionProviderRepresentation> {
|
||||
@Override
|
||||
public int compare(RequiredActionProviderRepresentation o1, RequiredActionProviderRepresentation o2) {
|
||||
return o1.getAlias().compareTo(o2.getAlias());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,10 +17,12 @@
|
|||
|
||||
package org.keycloak.testsuite.admin.client;
|
||||
|
||||
import java.util.List;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractAuthTest;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
||||
|
@ -34,29 +36,35 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
|
|||
return testRealmResource().toRepresentation();
|
||||
}
|
||||
|
||||
protected void createOidcClient(String name) {
|
||||
// returns UserRepresentation retrieved from server, with all fields, including id
|
||||
protected UserRepresentation getFullUserRep(String userName) {
|
||||
List<UserRepresentation> results = testRealmResource().users().search(userName, null, null, null, null, null);
|
||||
if (results.size() != 1) throw new RuntimeException("Did not find single user with username " + userName);
|
||||
return results.get(0);
|
||||
}
|
||||
|
||||
protected String createOidcClient(String name) {
|
||||
ClientRepresentation clientRep = new ClientRepresentation();
|
||||
clientRep.setClientId(name);
|
||||
clientRep.setName(name);
|
||||
clientRep.setRootUrl("foo");
|
||||
clientRep.setProtocol("openid-connect");
|
||||
createClient(clientRep);
|
||||
return createClient(clientRep);
|
||||
}
|
||||
|
||||
protected void createSamlClient(String name) {
|
||||
protected String createSamlClient(String name) {
|
||||
ClientRepresentation clientRep = new ClientRepresentation();
|
||||
clientRep.setClientId(name);
|
||||
clientRep.setName(name);
|
||||
clientRep.setProtocol("saml");
|
||||
clientRep.setAdminUrl("samlEndpoint");
|
||||
createClient(clientRep);
|
||||
return createClient(clientRep);
|
||||
}
|
||||
|
||||
protected void createClient(ClientRepresentation clientRep) {
|
||||
protected String createClient(ClientRepresentation clientRep) {
|
||||
Response resp = testRealmResource().clients().create(clientRep);
|
||||
// for some reason, findAll() will later fail unless readEntity is called here
|
||||
resp.readEntity(String.class);
|
||||
//testRealmResource().clients().findAll();
|
||||
resp.close();
|
||||
return ApiUtil.getCreatedId(resp);
|
||||
}
|
||||
|
||||
protected ClientRepresentation findClientRepresentation(String name) {
|
||||
|
@ -69,4 +77,8 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
|
|||
return ApiUtil.findClientResourceByName(testRealmResource(), name);
|
||||
}
|
||||
|
||||
protected ClientResource findClientResourceById(String id) {
|
||||
return ApiUtil.findClientResourceByClientId(testRealmResource(), id);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.admin.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
public class ClientProtocolMapperTest extends AbstractClientTest {
|
||||
|
||||
private ClientResource oidcClientRsc;
|
||||
private ProtocolMappersResource oidcMappersRsc;
|
||||
private ClientResource samlClientRsc;
|
||||
private ProtocolMappersResource samlMappersRsc;
|
||||
|
||||
private Map<String, List<ProtocolMapperRepresentation>> builtinMappers = null;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
createOidcClient("oidcMapperClient");
|
||||
oidcClientRsc = findClientResource("oidcMapperClient");
|
||||
oidcMappersRsc = oidcClientRsc.getProtocolMappers();
|
||||
|
||||
createSamlClient("samlMapperClient");
|
||||
samlClientRsc = findClientResource("samlMapperClient");
|
||||
samlMappersRsc = samlClientRsc.getProtocolMappers();
|
||||
|
||||
builtinMappers = adminClient.serverInfo().getInfo().getBuiltinProtocolMappers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
oidcClientRsc.remove();
|
||||
samlClientRsc.remove();
|
||||
}
|
||||
|
||||
private ProtocolMapperRepresentation makeMapper(String protocol, String name, String mapperType, Map<String, String> config) {
|
||||
ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation();
|
||||
rep.setProtocol(protocol);
|
||||
rep.setName(name);
|
||||
rep.setProtocolMapper(mapperType);
|
||||
rep.setConfig(config);
|
||||
rep.setConsentRequired(true);
|
||||
rep.setConsentText("Test Consent Text");
|
||||
return rep;
|
||||
}
|
||||
|
||||
private ProtocolMapperRepresentation makeSamlMapper(String name) {
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put("role", "account.view-profile");
|
||||
config.put("new.role.name", "new-role-name");
|
||||
return makeMapper("saml", name, "saml-role-name-mapper", config);
|
||||
}
|
||||
|
||||
private ProtocolMapperRepresentation makeOidcMapper(String name) {
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put("role", "myrole");
|
||||
return makeMapper("openid-connect", name, "oidc-hardcoded-role-mapper", config);
|
||||
}
|
||||
|
||||
private void assertEqualMappers(ProtocolMapperRepresentation original, ProtocolMapperRepresentation created) {
|
||||
assertNotNull(created);
|
||||
assertEquals(original.getName(), created.getName());
|
||||
assertEquals(original.getConfig(), created.getConfig());
|
||||
assertEquals(original.getConsentText(), created.getConsentText());
|
||||
assertEquals(original.isConsentRequired(), created.isConsentRequired());
|
||||
assertEquals(original.getProtocol(), created.getProtocol());
|
||||
assertEquals(original.getProtocolMapper(), created.getProtocolMapper());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMappersList() {
|
||||
assertFalse(oidcMappersRsc.getMappers().isEmpty());
|
||||
assertFalse(samlMappersRsc.getMappers().isEmpty());
|
||||
}
|
||||
|
||||
private boolean containsMapper(List<ProtocolMapperRepresentation> mappers, ProtocolMapperRepresentation mapper) {
|
||||
for (ProtocolMapperRepresentation listedMapper : mappers) {
|
||||
if (listedMapper.getName().equals(mapper.getName())) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ProtocolMapperRepresentation> mappersToAdd(List<ProtocolMapperRepresentation> oldMappers,
|
||||
List<ProtocolMapperRepresentation> builtins) {
|
||||
List<ProtocolMapperRepresentation> mappersToAdd = new ArrayList<>();
|
||||
for (ProtocolMapperRepresentation builtin : builtins) {
|
||||
if (!containsMapper(oldMappers, builtin)) mappersToAdd.add(builtin);
|
||||
}
|
||||
|
||||
return mappersToAdd;
|
||||
}
|
||||
|
||||
private void testAddAllBuiltinMappers(ProtocolMappersResource resource, String resourceName) {
|
||||
List<ProtocolMapperRepresentation> oldMappers = resource.getMappersPerProtocol(resourceName);
|
||||
List<ProtocolMapperRepresentation> builtins = builtinMappers.get(resourceName);
|
||||
|
||||
List<ProtocolMapperRepresentation> mappersToAdd = mappersToAdd(oldMappers, builtins);
|
||||
|
||||
// This is used by admin console to add builtin mappers
|
||||
resource.createMapper(mappersToAdd);
|
||||
|
||||
List<ProtocolMapperRepresentation> newMappers = resource.getMappersPerProtocol(resourceName);
|
||||
assertEquals(oldMappers.size() + mappersToAdd.size(), newMappers.size());
|
||||
|
||||
for (ProtocolMapperRepresentation rep : mappersToAdd) {
|
||||
assertTrue(containsMapper(newMappers, rep));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateOidcMappersFromList() {
|
||||
testAddAllBuiltinMappers(oidcMappersRsc, "openid-connect");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSamlMappersFromList() {
|
||||
testAddAllBuiltinMappers(samlMappersRsc, "saml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSamlProtocolMapper() {
|
||||
|
||||
//{"protocol":"saml",
|
||||
// "config":{"role":"account.view-profile","new.role.name":"new-role-name"},
|
||||
// "consentRequired":true,
|
||||
// "consentText":"My consent text",
|
||||
// "name":"saml-role-name-maper",
|
||||
// "protocolMapper":"saml-role-name-mapper"}
|
||||
ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper");
|
||||
|
||||
int totalMappers = samlMappersRsc.getMappers().size();
|
||||
int totalSamlMappers = samlMappersRsc.getMappersPerProtocol("saml").size();
|
||||
Response resp = samlMappersRsc.createMapper(rep);
|
||||
resp.close();
|
||||
assertEquals(totalMappers + 1, samlMappersRsc.getMappers().size());
|
||||
assertEquals(totalSamlMappers + 1, samlMappersRsc.getMappersPerProtocol("saml").size());
|
||||
|
||||
String createdId = ApiUtil.getCreatedId(resp);
|
||||
ProtocolMapperRepresentation created = samlMappersRsc.getMapperById(createdId);
|
||||
assertEqualMappers(rep, created);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateOidcProtocolMapper() {
|
||||
//{"protocol":"openid-connect",
|
||||
// "config":{"role":"myrole"},
|
||||
// "consentRequired":true,
|
||||
// "consentText":"My consent text",
|
||||
// "name":"oidc-hardcoded-role-mapper",
|
||||
// "protocolMapper":"oidc-hardcoded-role-mapper"}
|
||||
ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper");
|
||||
|
||||
int totalMappers = oidcMappersRsc.getMappers().size();
|
||||
int totalOidcMappers = oidcMappersRsc.getMappersPerProtocol("openid-connect").size();
|
||||
Response resp = oidcMappersRsc.createMapper(rep);
|
||||
resp.close();
|
||||
assertEquals(totalMappers + 1, oidcMappersRsc.getMappers().size());
|
||||
assertEquals(totalOidcMappers + 1, oidcMappersRsc.getMappersPerProtocol("openid-connect").size());
|
||||
|
||||
String createdId = ApiUtil.getCreatedId(resp);
|
||||
ProtocolMapperRepresentation created = oidcMappersRsc.getMapperById(createdId);//findByName(samlMappersRsc, "saml-role-name-mapper");
|
||||
assertEqualMappers(rep, created);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateSamlMapper() {
|
||||
ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper2");
|
||||
|
||||
Response resp = samlMappersRsc.createMapper(rep);
|
||||
resp.close();
|
||||
|
||||
String createdId = ApiUtil.getCreatedId(resp);
|
||||
|
||||
rep.getConfig().put("role", "account.manage-account");
|
||||
rep.setId(createdId);
|
||||
rep.setConsentRequired(false);
|
||||
samlMappersRsc.update(createdId, rep);
|
||||
|
||||
ProtocolMapperRepresentation updated = samlMappersRsc.getMapperById(createdId);
|
||||
assertEqualMappers(rep, updated);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOidcMapper() {
|
||||
ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper2");
|
||||
|
||||
Response resp = oidcMappersRsc.createMapper(rep);
|
||||
resp.close();
|
||||
|
||||
String createdId = ApiUtil.getCreatedId(resp);
|
||||
|
||||
rep.getConfig().put("role", "myotherrole");
|
||||
rep.setId(createdId);
|
||||
rep.setConsentRequired(false);
|
||||
oidcMappersRsc.update(createdId, rep);
|
||||
|
||||
ProtocolMapperRepresentation updated = oidcMappersRsc.getMapperById(createdId);
|
||||
assertEqualMappers(rep, updated);
|
||||
}
|
||||
|
||||
@Test (expected = NotFoundException.class)
|
||||
public void testDeleteSamlMapper() {
|
||||
ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper3");
|
||||
|
||||
Response resp = samlMappersRsc.createMapper(rep);
|
||||
resp.close();
|
||||
|
||||
String createdId = ApiUtil.getCreatedId(resp);
|
||||
|
||||
samlMappersRsc.delete(createdId);
|
||||
|
||||
samlMappersRsc.getMapperById(createdId);
|
||||
}
|
||||
|
||||
@Test (expected = NotFoundException.class)
|
||||
public void testDeleteOidcMapper() {
|
||||
ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper3");
|
||||
|
||||
Response resp = oidcMappersRsc.createMapper(rep);
|
||||
resp.close();
|
||||
|
||||
String createdId = ApiUtil.getCreatedId(resp);
|
||||
|
||||
oidcMappersRsc.delete(createdId);
|
||||
|
||||
oidcMappersRsc.getMapperById(createdId);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.testsuite.admin.client;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
|
@ -42,16 +43,17 @@ public class ClientRolesTest extends AbstractClientTest {
|
|||
rolesRsc = clientRsc.roles();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
clientRsc.remove();
|
||||
}
|
||||
|
||||
private RoleRepresentation makeRole(String name) {
|
||||
RoleRepresentation role = new RoleRepresentation();
|
||||
role.setName(name);
|
||||
return role;
|
||||
}
|
||||
|
||||
/* private boolean hasRole(RolesResource rolesRsc, String name) {
|
||||
return rolesRsc.get(name) != null;
|
||||
}*/
|
||||
|
||||
private boolean hasRole(RolesResource rolesRsc, String name) {
|
||||
for (RoleRepresentation role : rolesRsc.list()) {
|
||||
if (role.getName().equals(name)) return true;
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.admin.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
public class CredentialsTest extends AbstractClientTest {
|
||||
|
||||
private ClientResource accountClient;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
accountClient = findClientResourceById("account");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndRegenerateSecret() {
|
||||
CredentialRepresentation oldCredential = accountClient.getSecret();
|
||||
CredentialRepresentation newCredential = accountClient.generateNewSecret();
|
||||
assertNotNull(oldCredential);
|
||||
assertNotNull(newCredential);
|
||||
assertNotEquals(newCredential.getValue(), oldCredential.getValue());
|
||||
assertEquals(newCredential.getValue(), accountClient.getSecret().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndRegenerateRegistrationAccessToken() {
|
||||
ClientRepresentation rep = accountClient.toRepresentation();
|
||||
String oldToken = rep.getRegistrationAccessToken();
|
||||
String newToken = accountClient.regenerateRegistrationAccessToken().getRegistrationAccessToken();
|
||||
assertNull(oldToken); // registration access token not saved in ClientRep
|
||||
assertNotNull(newToken); // it's only available via regenerateRegistrationAccessToken()
|
||||
assertNull(accountClient.toRepresentation().getRegistrationAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCertificateResource() {
|
||||
ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
|
||||
CertificateRepresentation cert = certRsc.generate();
|
||||
CertificateRepresentation certFromGet = certRsc.getKeyInfo();
|
||||
assertEquals(cert.getCertificate(), certFromGet.getCertificate());
|
||||
assertEquals(cert.getPrivateKey(), certFromGet.getPrivateKey());
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.testsuite.admin.client;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
|
@ -47,6 +48,12 @@ public class InstallationTest extends AbstractClientTest {
|
|||
samlClient = findClientResource(SAML_NAME);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
oidcClient.remove();
|
||||
samlClient.remove();
|
||||
}
|
||||
|
||||
private String authServerUrl() {
|
||||
return AuthServerTestEnricher.getAuthServerContextRoot() + "/auth";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.admin.client;
|
||||
|
||||
import java.util.List;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.testsuite.auth.page.account.AccountManagement;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
public class SessionTest extends AbstractClientTest {
|
||||
private static boolean testUserCreated = false;
|
||||
|
||||
@Page
|
||||
protected AccountManagement testRealmAccountManagementPage;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
// make user test user exists in test realm
|
||||
if (!testUserCreated) createTestUserWithAdminClient();
|
||||
testUserCreated = true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAppSessionCount() {
|
||||
ClientResource accountClient = findClientResourceById("account");
|
||||
int sessionCount = accountClient.getApplicationSessionCount().get("count");
|
||||
assertEquals(0, sessionCount);
|
||||
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
loginPage.form().login(testUser);
|
||||
|
||||
sessionCount = accountClient.getApplicationSessionCount().get("count");
|
||||
assertEquals(1, sessionCount);
|
||||
|
||||
testRealmAccountManagementPage.signOut();
|
||||
|
||||
sessionCount = accountClient.getApplicationSessionCount().get("count");
|
||||
assertEquals(0, sessionCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserSessions() {
|
||||
//List<java.util.Map<String, String>> stats = this.testRealmResource().getClientSessionStats();
|
||||
ClientResource account = findClientResourceById("account");
|
||||
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
loginPage.form().login(testUser);
|
||||
|
||||
List<UserSessionRepresentation> sessions = account.getUserSessions(0, 5);
|
||||
assertEquals(1, sessions.size());
|
||||
|
||||
UserSessionRepresentation rep = sessions.get(0);
|
||||
|
||||
UserRepresentation testUserRep = getFullUserRep(testUser.getUsername());
|
||||
assertEquals(testUserRep.getId(), rep.getUserId());
|
||||
assertEquals(testUserRep.getUsername(), rep.getUsername());
|
||||
|
||||
String clientId = account.toRepresentation().getId();
|
||||
assertEquals("account", rep.getClients().get(clientId));
|
||||
assertNotNull(rep.getIpAddress());
|
||||
assertNotNull(rep.getLastAccess());
|
||||
assertNotNull(rep.getStart());
|
||||
|
||||
testRealmAccountManagementPage.signOut();
|
||||
}
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
package org.keycloak.testsuite.cluster;
|
||||
|
||||
import java.util.List;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
import org.openqa.selenium.Cookie;
|
||||
import org.keycloak.testsuite.page.AbstractPage;
|
||||
import org.keycloak.testsuite.page.PageWithLogOutAction;
|
||||
import org.junit.Before;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -20,77 +22,131 @@ import org.openqa.selenium.Cookie;
|
|||
public class SessionFailoverClusterTest extends AbstractClusterTest {
|
||||
|
||||
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
|
||||
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
||||
|
||||
public static final Integer SESSION_CACHE_OWNERS = Integer.parseInt(System.getProperty("session.cache.owners", "1"));
|
||||
public static final Integer OFFLINE_SESSION_CACHE_OWNERS = Integer.parseInt(System.getProperty("offline.session.cache.owners", "1"));
|
||||
public static final Integer LOGIN_FAILURES_CACHE_OWNERS = Integer.parseInt(System.getProperty("login.failure.cache.owners", "1"));
|
||||
|
||||
public static final Integer REBALANCE_WAIT = Integer.parseInt(System.getProperty("rebalance.wait", "5000"));
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeSessionFailover() {
|
||||
log.info("Initial node failure");
|
||||
failure();
|
||||
pause(REBALANCE_WAIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("work in progress") // only works with owners="2" at the moment
|
||||
public void sessionFailover() {
|
||||
|
||||
boolean expectSuccessfulFailover = SESSION_CACHE_OWNERS >= getClusterSize();
|
||||
|
||||
log.info("SESSION FAILOVER TEST: cluster size = " + getClusterSize() + ", session-cache owners = " + SESSION_CACHE_OWNERS
|
||||
+ " --> Testsing for " + (expectSuccessfulFailover ? "" : "UN") + "SUCCESSFUL session failover.");
|
||||
|
||||
assertEquals(2, getClusterSize());
|
||||
|
||||
sessionFailover(expectSuccessfulFailover);
|
||||
}
|
||||
|
||||
protected void sessionFailover(boolean expectSuccessfulFailover) {
|
||||
|
||||
// LOGIN
|
||||
accountPage.navigateTo();
|
||||
driver.navigate().refresh();
|
||||
pause(3000);
|
||||
loginPage.form().login(ADMIN, ADMIN);
|
||||
assertCurrentUrlStartsWith(accountPage);
|
||||
Cookie sessionCookie = login(accountPage);
|
||||
|
||||
Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNotNull(sessionCookie);
|
||||
switchFailedNode();
|
||||
|
||||
failure();
|
||||
// VERIFY
|
||||
if (expectSuccessfulFailover) {
|
||||
verifyLoggedIn(accountPage, sessionCookie);
|
||||
} else {
|
||||
verifyLoggedOut(accountPage);
|
||||
// FIXME test fails if I put re-login here
|
||||
}
|
||||
|
||||
// check if session survived backend failure
|
||||
switchFailedNode();
|
||||
|
||||
driver.navigate().refresh();
|
||||
pause(3000);
|
||||
|
||||
assertCurrentUrlStartsWith(accountPage);
|
||||
Cookie sessionCookieAfterFailover = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNotNull(sessionCookieAfterFailover);
|
||||
assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
|
||||
|
||||
failback();
|
||||
iterateCurrentFailNode();
|
||||
|
||||
// check if session survived backend failback
|
||||
driver.navigate().refresh();
|
||||
pause(3000);
|
||||
assertCurrentUrlStartsWith(accountPage);
|
||||
Cookie sessionCookieAfterFailback = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNotNull(sessionCookieAfterFailback);
|
||||
assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
|
||||
// VERIFY again
|
||||
if (expectSuccessfulFailover) {
|
||||
verifyLoggedIn(accountPage, sessionCookie);
|
||||
} else {
|
||||
verifyLoggedOut(accountPage);
|
||||
login(accountPage);
|
||||
}
|
||||
|
||||
// LOGOUT
|
||||
accountPage.navigateTo();
|
||||
accountPage.signOut();
|
||||
logout(accountPage);
|
||||
verifyLoggedOut(accountPage);
|
||||
|
||||
assertCurrentUrlDoesntStartWith(accountPage);
|
||||
masterRealmPage.navigateTo();
|
||||
sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNull(sessionCookie);
|
||||
switchFailedNode();
|
||||
|
||||
failure();
|
||||
// VERIFY
|
||||
verifyLoggedOut(accountPage);
|
||||
|
||||
// check if session survived backend failure
|
||||
driver.navigate().refresh();
|
||||
pause(3000);
|
||||
assertCurrentUrlDoesntStartWith(accountPage);
|
||||
masterRealmPage.navigateTo();
|
||||
sessionCookieAfterFailover = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNull(sessionCookieAfterFailover);
|
||||
}
|
||||
|
||||
/**
|
||||
* failure --> failback --> failure of next node
|
||||
*/
|
||||
protected void switchFailedNode() {
|
||||
assertFalse(controller.isStarted(getCurrentFailNode().getQualifier()));
|
||||
|
||||
failback();
|
||||
pause(REBALANCE_WAIT);
|
||||
|
||||
// check if session survived backend failback
|
||||
driver.navigate().refresh();
|
||||
pause(3000);
|
||||
assertCurrentUrlDoesntStartWith(accountPage);
|
||||
iterateCurrentFailNode();
|
||||
|
||||
failure();
|
||||
pause(REBALANCE_WAIT);
|
||||
|
||||
assertFalse(controller.isStarted(getCurrentFailNode().getQualifier()));
|
||||
}
|
||||
|
||||
protected Cookie login(AbstractPage targetPage) {
|
||||
targetPage.navigateTo();
|
||||
assertCurrentUrlStartsWith(loginPage);
|
||||
loginPage.form().login(ADMIN, ADMIN);
|
||||
assertCurrentUrlStartsWith(targetPage);
|
||||
Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNotNull(sessionCookie);
|
||||
return sessionCookie;
|
||||
}
|
||||
|
||||
protected void logout(AbstractPage targetPage) {
|
||||
if (!(targetPage instanceof PageWithLogOutAction)) {
|
||||
throw new IllegalArgumentException(targetPage.getClass().getSimpleName() + " must implement PageWithLogOutAction interface");
|
||||
}
|
||||
targetPage.navigateTo();
|
||||
assertCurrentUrlStartsWith(targetPage);
|
||||
((PageWithLogOutAction) targetPage).logOut();
|
||||
}
|
||||
|
||||
protected Cookie verifyLoggedIn(AbstractPage targetPage, Cookie sessionCookieForVerification) {
|
||||
// verify on realm path
|
||||
masterRealmPage.navigateTo();
|
||||
sessionCookieAfterFailback = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNull(sessionCookieAfterFailback);
|
||||
Cookie sessionCookieOnRealmPath = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNotNull(sessionCookieOnRealmPath);
|
||||
assertEquals(sessionCookieOnRealmPath.getValue(), sessionCookieForVerification.getValue());
|
||||
// verify on target page
|
||||
targetPage.navigateTo();
|
||||
assertCurrentUrlStartsWith(targetPage);
|
||||
Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNotNull(sessionCookie);
|
||||
assertEquals(sessionCookie.getValue(), sessionCookieForVerification.getValue());
|
||||
return sessionCookie;
|
||||
}
|
||||
|
||||
protected void verifyLoggedOut(AbstractPage targetPage) {
|
||||
// verify on target page
|
||||
targetPage.navigateTo();
|
||||
driver.navigate().refresh();
|
||||
assertCurrentUrlStartsWith(loginPage);
|
||||
Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
assertNull(sessionCookie);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
<property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
|
||||
<property name="managementPort">${auth.server.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||
<property name="javaHome">${auth.server.java.home}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
|
||||
|
@ -134,9 +135,70 @@
|
|||
<property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
|
||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||
<property name="managementPort">${auth.server.management.port}</property>
|
||||
<property name="javaHome">${auth.server.java.home}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
|
||||
<group qualifier="auth-server-eap7-cluster">
|
||||
<container qualifier="auth-server-eap7-balancer" mode="suite" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.eap7.cluster}</property>
|
||||
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
|
||||
<property name="jbossHome">${keycloak.balancer.home}</property>
|
||||
<property name="jbossArguments">
|
||||
-Djboss.socket.binding.port-offset=${auth.server.port.offset}
|
||||
</property>
|
||||
<property name="javaVmArguments">
|
||||
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||
-Djava.net.preferIPv4Stack=true
|
||||
</property>
|
||||
<property name="outputToConsole">${frontend.console.output}</property>
|
||||
<property name="managementPort">${auth.server.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
<container qualifier="auth-server-eap7-backend1" mode="manual" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.eap7.cluster}</property>
|
||||
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
|
||||
<property name="jbossHome">${keycloak.backend1.home}</property>
|
||||
<property name="serverConfig">standalone-ha.xml</property>
|
||||
<property name="jbossArguments">
|
||||
-Djboss.socket.binding.port-offset=${auth.server.backend1.port.offset}
|
||||
-Djboss.node.name=node1
|
||||
${adapter.test.props}
|
||||
</property>
|
||||
<property name="javaVmArguments">
|
||||
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||
-Djava.net.preferIPv4Stack=true
|
||||
</property>
|
||||
<property name="outputToConsole">${backends.console.output}</property>
|
||||
<property name="managementPort">${auth.server.backend1.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
<container qualifier="auth-server-eap7-backend2" mode="manual" >
|
||||
<configuration>
|
||||
<property name="enabled">${auth.server.eap7.cluster}</property>
|
||||
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
|
||||
<property name="jbossHome">${keycloak.backend2.home}</property>
|
||||
<property name="serverConfig">standalone-ha.xml</property>
|
||||
<property name="jbossArguments">
|
||||
-Djboss.socket.binding.port-offset=${auth.server.backend2.port.offset}
|
||||
-Djboss.node.name=node2
|
||||
${adapter.test.props}
|
||||
</property>
|
||||
<property name="javaVmArguments">
|
||||
-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
|
||||
-Djava.net.preferIPv4Stack=true
|
||||
</property>
|
||||
<property name="outputToConsole">${backends.console.output}</property>
|
||||
<property name="managementPort">${auth.server.backend2.management.port}</property>
|
||||
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
|
||||
</configuration>
|
||||
</container>
|
||||
</group>
|
||||
|
||||
<!-- PREVIOUS VERSIONS OF KEYCLOAK FOR MIGRATION TESTS -->
|
||||
|
||||
<container qualifier="auth-server-wildfly-kc16" mode="manual" >
|
||||
|
|
|
@ -72,9 +72,9 @@ public class CreateClientForm extends Form {
|
|||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
Timer.time();
|
||||
Timer.DEFAULT.reset();
|
||||
protocolSelect.selectByVisibleText(protocol);
|
||||
Timer.time("clientSettings.setProtocol()");
|
||||
Timer.DEFAULT.reset("clientSettings.setProtocol()");
|
||||
}
|
||||
|
||||
public class SAMLClientSettingsForm extends Form {
|
||||
|
|
|
@ -187,19 +187,19 @@ public class ClientSettingsForm extends CreateClientForm {
|
|||
}
|
||||
|
||||
public void setRedirectUris(List<String> redirectUris) {
|
||||
Timer.time();
|
||||
Timer.DEFAULT.reset();
|
||||
while (!deleteRedirectUriIcons.isEmpty()) {
|
||||
deleteRedirectUriIcons.get(0).click();
|
||||
pause(100);
|
||||
}
|
||||
Timer.time("deleteRedirectUris");
|
||||
Timer.DEFAULT.reset("deleteRedirectUris");
|
||||
if (redirectUris != null) {
|
||||
for (String redirectUri : redirectUris) {
|
||||
addRedirectUri(redirectUri);
|
||||
pause(100);
|
||||
}
|
||||
}
|
||||
Timer.time("addRedirectUris");
|
||||
Timer.DEFAULT.reset("addRedirectUris");
|
||||
}
|
||||
|
||||
public boolean isStandardFlowEnabled() {
|
||||
|
|
|
@ -166,10 +166,10 @@ public class ClientSettingsTest extends AbstractClientTest {
|
|||
for (int i = 0; i < count; i++) {
|
||||
String clientId = String.format("%s%02d", clientIdPrefix, i);
|
||||
ClientRepresentation cr = createOidcClientRep(CONFIDENTIAL, clientId, "http://example.test/*");
|
||||
Timer.time();
|
||||
Timer.DEFAULT.reset();
|
||||
Response r = testRealmResource().clients().create(cr);
|
||||
r.close();
|
||||
Timer.time("create client");
|
||||
Timer.DEFAULT.reset("create client");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import org.junit.Test;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.admin.Users;
|
||||
import org.keycloak.testsuite.console.AbstractConsoleTest;
|
||||
import org.keycloak.testsuite.console.page.clients.Client;
|
||||
import org.keycloak.testsuite.console.page.events.Config;
|
||||
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
||||
import org.openqa.selenium.By;
|
||||
|
@ -16,6 +15,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Ignore;
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
|
||||
/**
|
||||
|
|
|
@ -41,7 +41,7 @@ import static org.junit.Assert.*;
|
|||
public class SecurityDefensesTest extends AbstractRealmTest {
|
||||
|
||||
public static final String INVALID_PWD_MSG = "Invalid username or password.";
|
||||
public static final String ACC_DISABLED_MSG = "Account is temporarily disabled, contact admin or try again later.";
|
||||
public static final String ACC_DISABLED_MSG = "Invalid username or password.";
|
||||
public static final short ATTEMPTS_BAD_PWD = 2;
|
||||
public static final short ATTEMPTS_GOOD_PWD = 1;
|
||||
|
||||
|
|
|
@ -121,13 +121,13 @@ public class RealmRolesTest extends AbstractRolesTest {
|
|||
}
|
||||
|
||||
public void createTestRoles(String namePrefix, int count) {
|
||||
Timer.time();
|
||||
Timer.DEFAULT.reset();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String roleName = String.format("%s%02d", namePrefix, i);
|
||||
RoleRepresentation rr = new RoleRepresentation(roleName, "", false);
|
||||
testRealmResource().roles().create(rr);
|
||||
}
|
||||
Timer.time("create " + count + " roles");
|
||||
Timer.DEFAULT.reset("create " + count + " roles");
|
||||
}
|
||||
|
||||
// @Test
|
||||
|
|
|
@ -21,7 +21,7 @@ public class UsersTest extends AbstractUserTest {
|
|||
}
|
||||
|
||||
public void createTestUsers(String usernamePrefix, int count) {
|
||||
// Timer.time();
|
||||
// Timer.DEFAULT.reset();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String username = String.format("%s%03d", usernamePrefix, i);
|
||||
UserRepresentation u = createUserRepresentation(
|
||||
|
@ -30,13 +30,13 @@ public class UsersTest extends AbstractUserTest {
|
|||
"First",
|
||||
"Last",
|
||||
true);
|
||||
Timer.time();
|
||||
Timer.DEFAULT.reset();
|
||||
Response r = testRealmResource().users().create(u);
|
||||
String id = getCreatedId(r);
|
||||
r.close();
|
||||
Timer.time("create user");
|
||||
Timer.DEFAULT.reset("create user");
|
||||
}
|
||||
// Timer.time("create " + count + " users");
|
||||
// Timer.DEFAULT.reset("create " + count + " users");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -57,16 +57,18 @@
|
|||
<browser>phantomjs</browser>
|
||||
<firefox_binary>/usr/bin/firefox</firefox_binary>
|
||||
|
||||
<arquillian-core.version>1.1.8.Final</arquillian-core.version>
|
||||
<selenium.version>2.45.0</selenium.version>
|
||||
<arquillian-drone.version>2.0.0.Alpha4</arquillian-drone.version>
|
||||
<arquillian-core.version>1.1.11.Final</arquillian-core.version>
|
||||
<selenium.version>2.52.0</selenium.version>
|
||||
<arquillian-drone.version>2.0.0.Beta1</arquillian-drone.version>
|
||||
<arquillian-graphene.version>2.1.0.Alpha2</arquillian-graphene.version>
|
||||
<arquillian-wildfly-container.version>8.2.0.Final</arquillian-wildfly-container.version>
|
||||
<version.shrinkwrap.resolvers>2.1.1</version.shrinkwrap.resolvers>
|
||||
<version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
|
||||
|
||||
<frontend.console.output>true</frontend.console.output>
|
||||
<backends.console.output>true</backends.console.output>
|
||||
|
||||
<auth.server.java.home>${java.home}</auth.server.java.home>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -120,6 +122,7 @@
|
|||
<auth.server.management.port>${auth.server.management.port}</auth.server.management.port>
|
||||
<auth.server.management.port.jmx>${auth.server.management.port.jmx}</auth.server.management.port.jmx>
|
||||
<auth.server.ssl.required>${auth.server.ssl.required}</auth.server.ssl.required>
|
||||
<auth.server.java.home>${auth.server.java.home}</auth.server.java.home>
|
||||
<startup.timeout.sec>${startup.timeout.sec}</startup.timeout.sec>
|
||||
<jboss.server.config.dir>${jboss.server.config.dir}</jboss.server.config.dir>
|
||||
<frontend.console.output>${frontend.console.output}</frontend.console.output>
|
||||
|
@ -221,7 +224,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jboss.arquillian.graphene</groupId>
|
||||
<artifactId>arquillian-browser-screenshooter</artifactId>
|
||||
<version>2.1.0.Alpha3</version><!-- TODO upgrade <arquillian-graphene.version> and use ${arquillian-graphene.version} -->
|
||||
<version>${arquillian-graphene.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -661,6 +664,7 @@
|
|||
</build>
|
||||
</profile>
|
||||
|
||||
|
||||
<profile>
|
||||
<id>auth-server-eap7</id>
|
||||
<properties>
|
||||
|
@ -740,6 +744,129 @@
|
|||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>auth-server-eap7-cluster</id>
|
||||
<properties>
|
||||
<!--disable exclusion pattern for cluster test which is enabled by default in base/pom.xml-->
|
||||
<exclude.cluster>-</exclude.cluster>
|
||||
|
||||
<auth.server.container>auth-server-eap7-cluster</auth.server.container>
|
||||
<startup.timeout.sec>300</startup.timeout.sec>
|
||||
<adapter.test.props/>
|
||||
<h2.version>1.3.173</h2.version>
|
||||
|
||||
<keycloak.balancer.home>${containers.home}/balancer/wildfly-balancer-${project.version}</keycloak.balancer.home>
|
||||
<keycloak.backend1.home>${containers.home}/node1/keycloak-${version.server.dist}</keycloak.backend1.home>
|
||||
<keycloak.backend2.home>${containers.home}/node2/keycloak-${version.server.dist}</keycloak.backend2.home>
|
||||
|
||||
<keycloak.home>${keycloak.backend1.home}</keycloak.home>
|
||||
<jboss.server.config.dir>${keycloak.home}/standalone/configuration</jboss.server.config.dir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wildfly</groupId>
|
||||
<artifactId>wildfly-arquillian-container-managed</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-properties</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireProperty>
|
||||
<property>version.server.dist</property>
|
||||
</requireProperty>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<run.h2>true</run.h2>
|
||||
|
||||
<auth.server.eap7.cluster>true</auth.server.eap7.cluster>
|
||||
<auth.server.undertow>false</auth.server.undertow>
|
||||
<adapter.test.props>${adapter.test.props}</adapter.test.props>
|
||||
|
||||
<keycloak.balancer.home>${keycloak.balancer.home}</keycloak.balancer.home>
|
||||
<keycloak.backend1.home>${keycloak.backend1.home}</keycloak.backend1.home>
|
||||
<keycloak.backend2.home>${keycloak.backend2.home}</keycloak.backend2.home>
|
||||
|
||||
<!--100-->
|
||||
<auth.server.backend1.port.offset>101</auth.server.backend1.port.offset>
|
||||
<auth.server.backend2.port.offset>102</auth.server.backend2.port.offset>
|
||||
<!--8180-->
|
||||
<auth.server.backend1.http.port>8181</auth.server.backend1.http.port>
|
||||
<auth.server.backend2.http.port>8182</auth.server.backend2.http.port>
|
||||
<!--8543-->
|
||||
<auth.server.backend1.https.port>8544</auth.server.backend1.https.port>
|
||||
<auth.server.backend2.https.port>8545</auth.server.backend2.https.port>
|
||||
<!--10090-->
|
||||
<auth.server.backend1.management.port>10091</auth.server.backend1.management.port>
|
||||
<auth.server.backend2.management.port>10092</auth.server.backend2.management.port>
|
||||
<!--10099-->
|
||||
<auth.server.backend1.management.port.jmx>10100</auth.server.backend1.management.port.jmx>
|
||||
<auth.server.backend2.management.port.jmx>10101</auth.server.backend2.management.port.jmx>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-auth-server-wildfly</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-server-wildfly-balancer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<outputDirectory>${containers.home}/balancer</outputDirectory>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-server-eap7</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<outputDirectory>${containers.home}/node1</outputDirectory>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-server-eap7</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
<outputDirectory>${containers.home}/node2</outputDirectory>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<overWriteIfNewer>true</overWriteIfNewer>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<!-- Profiles for migration tests-->
|
||||
|
||||
<profile>
|
||||
|
|
|
@ -25,8 +25,10 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -37,7 +39,11 @@ import java.util.Set;
|
|||
*/
|
||||
public class DummyUserFederationProvider implements UserFederationProvider {
|
||||
|
||||
private static Map<String, UserModel> users = new HashMap<String, UserModel>();
|
||||
private final Map<String, UserModel> users;
|
||||
|
||||
public DummyUserFederationProvider(Map<String, UserModel> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
|
||||
|
@ -97,27 +103,39 @@ public class DummyUserFederationProvider implements UserFederationProvider {
|
|||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel local) {
|
||||
return false;
|
||||
String username = local.getUsername();
|
||||
return users.containsKey(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedCredentialTypes(UserModel user) {
|
||||
return Collections.emptySet();
|
||||
// Just user "test-user" is able to validate password with this federationProvider
|
||||
if (user.getUsername().equals("test-user")) {
|
||||
return Collections.singleton(UserCredentialModel.PASSWORD);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedCredentialTypes() {
|
||||
return Collections.emptySet();
|
||||
return Collections.singleton(UserCredentialModel.PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
if (user.getUsername().equals("test-user") && input.size() == 1) {
|
||||
UserCredentialModel password = input.get(0);
|
||||
if (password.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
return "secret".equals(password.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
||||
return false;
|
||||
return validCredentials(realm, user, Arrays.asList(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.UserFederationProvider;
|
|||
import org.keycloak.models.UserFederationProviderFactory;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserFederationSyncResult;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.ConfiguredProvider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
|
@ -43,9 +44,11 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
|
|||
private AtomicInteger fullSyncCounter = new AtomicInteger();
|
||||
private AtomicInteger changedSyncCounter = new AtomicInteger();
|
||||
|
||||
private Map<String, UserModel> users = new HashMap<String, UserModel>();
|
||||
|
||||
@Override
|
||||
public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
|
||||
return new DummyUserFederationProvider();
|
||||
return new DummyUserFederationProvider(users);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -57,7 +60,7 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
|
|||
|
||||
@Override
|
||||
public UserFederationProvider create(KeycloakSession session) {
|
||||
return new DummyUserFederationProvider();
|
||||
return new DummyUserFederationProvider(users);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -205,6 +205,10 @@ public class OAuthClient {
|
|||
return doGrantAccessTokenRequest(realm, username, password, null, clientId, clientSecret);
|
||||
}
|
||||
|
||||
public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password, String otp) throws Exception {
|
||||
return doGrantAccessTokenRequest(realm, username, password, otp, clientId, clientSecret);
|
||||
}
|
||||
|
||||
public AccessTokenResponse doGrantAccessTokenRequest(String realm, String username, String password, String totp,
|
||||
String clientId, String clientSecret) throws Exception {
|
||||
CloseableHttpClient client = new DefaultHttpClient();
|
||||
|
|
|
@ -59,6 +59,7 @@ import java.net.URI;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
||||
/**
|
||||
* Tests Undertow Adapter
|
||||
|
@ -598,7 +599,8 @@ public class AdapterTestStrategy extends ExternalResource {
|
|||
|
||||
// logout mposolda with admin client
|
||||
Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
|
||||
ApiUtil.findClientByClientId(keycloakAdmin.realm("demo"), "session-portal").logoutUser("mposolda");
|
||||
UserRepresentation mposolda = keycloakAdmin.realm("demo").users().search("mposolda", null, null, null, null, null).get(0);
|
||||
keycloakAdmin.realm("demo").users().get(mposolda.getId()).logout();
|
||||
|
||||
// bburke should be still logged with original httpSession in our browser window
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/session-portal");
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
|
|||
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
|
||||
import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
|
||||
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
|
@ -103,6 +104,9 @@ public abstract class AbstractIdentityProviderTest {
|
|||
@WebResource
|
||||
protected OAuthGrantPage grantPage;
|
||||
|
||||
@WebResource
|
||||
AccountUpdateProfilePage accountUpdateProfilePage;
|
||||
|
||||
@WebResource
|
||||
protected AccountPasswordPage changePasswordPage;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.broker;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
@ -39,9 +40,11 @@ import org.keycloak.models.FederatedIdentityModel;
|
|||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
||||
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
|
@ -461,74 +464,145 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
|
|||
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
|
||||
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
|
||||
|
||||
identityProviderModel.setStoreToken(true);
|
||||
setStoreToken(identityProviderModel, true);
|
||||
try {
|
||||
authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
|
||||
|
||||
authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
|
||||
brokerServerRule.stopSession(session, true);
|
||||
session = brokerServerRule.startSession();
|
||||
|
||||
brokerServerRule.stopSession(session, true);
|
||||
UserModel federatedUser = getFederatedUser();
|
||||
RealmModel realm = getRealm();
|
||||
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
|
||||
|
||||
assertFalse(federatedIdentities.isEmpty());
|
||||
assertEquals(1, federatedIdentities.size());
|
||||
|
||||
FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
|
||||
|
||||
assertNotNull(identityModel.getToken());
|
||||
|
||||
UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus();
|
||||
String accessToken = userSessionStatus.getAccessTokenString();
|
||||
URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
|
||||
final String authHeader = "Bearer " + accessToken;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
|
||||
Response response = tokenEndpoint.request().get();
|
||||
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
assertNotNull(response.readEntity(String.class));
|
||||
revokeGrant();
|
||||
|
||||
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
String currentUrl = this.driver.getCurrentUrl();
|
||||
System.out.println("after logout currentUrl: " + currentUrl);
|
||||
assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
|
||||
|
||||
unconfigureUserRetrieveToken("test-user");
|
||||
loginIDP("test-user");
|
||||
//authenticateWithIdentityProvider(identityProviderModel, "test-user");
|
||||
assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
|
||||
|
||||
userSessionStatus = retrieveSessionStatus();
|
||||
accessToken = userSessionStatus.getAccessTokenString();
|
||||
final String authHeader2 = "Bearer " + accessToken;
|
||||
ClientRequestFilter authFilter2 = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
|
||||
}
|
||||
};
|
||||
client = ClientBuilder.newBuilder().register(authFilter2).build();
|
||||
tokenEndpoint = client.target(tokenEndpointUrl);
|
||||
response = tokenEndpoint.request().get();
|
||||
|
||||
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||
|
||||
revokeGrant();
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
driver.navigate().to("http://localhost:8081/test-app");
|
||||
|
||||
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
|
||||
} finally {
|
||||
setStoreToken(identityProviderModel, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStoreToken(IdentityProviderModel identityProviderModel, boolean storeToken) {
|
||||
identityProviderModel.setStoreToken(storeToken);
|
||||
getRealm().updateIdentityProvider(identityProviderModel);
|
||||
|
||||
brokerServerRule.stopSession(session, storeToken);
|
||||
session = brokerServerRule.startSession();
|
||||
|
||||
UserModel federatedUser = getFederatedUser();
|
||||
RealmModel realm = getRealm();
|
||||
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
|
||||
|
||||
assertFalse(federatedIdentities.isEmpty());
|
||||
assertEquals(1, federatedIdentities.size());
|
||||
|
||||
FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
|
||||
|
||||
assertNotNull(identityModel.getToken());
|
||||
|
||||
UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus();
|
||||
String accessToken = userSessionStatus.getAccessTokenString();
|
||||
URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
|
||||
final String authHeader = "Bearer " + accessToken;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
|
||||
Response response = tokenEndpoint.request().get();
|
||||
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
assertNotNull(response.readEntity(String.class));
|
||||
revokeGrant();
|
||||
|
||||
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
String currentUrl = this.driver.getCurrentUrl();
|
||||
System.out.println("after logout currentUrl: " + currentUrl);
|
||||
assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
|
||||
|
||||
unconfigureUserRetrieveToken("test-user");
|
||||
loginIDP("test-user");
|
||||
//authenticateWithIdentityProvider(identityProviderModel, "test-user");
|
||||
assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
|
||||
|
||||
userSessionStatus = retrieveSessionStatus();
|
||||
accessToken = userSessionStatus.getAccessTokenString();
|
||||
final String authHeader2 = "Bearer " + accessToken;
|
||||
ClientRequestFilter authFilter2 = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
|
||||
}
|
||||
};
|
||||
client = ClientBuilder.newBuilder().register(authFilter2).build();
|
||||
tokenEndpoint = client.target(tokenEndpointUrl);
|
||||
response = tokenEndpoint.request().get();
|
||||
|
||||
assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||
|
||||
revokeGrant();
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
driver.navigate().to("http://localhost:8081/test-app");
|
||||
|
||||
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
|
||||
}
|
||||
|
||||
protected abstract void doAssertTokenRetrieval(String pageSource);
|
||||
|
||||
@Test
|
||||
public void testWithLinkedFederationProvider() throws Exception {
|
||||
// Add federationProvider to realm. It's configured with sync registrations
|
||||
RealmModel realm = getRealm();
|
||||
UserFederationProviderModel dummyModel = realm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, -1, 0);
|
||||
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
|
||||
|
||||
brokerServerRule.stopSession(session, true);
|
||||
session = brokerServerRule.startSession();
|
||||
|
||||
try {
|
||||
// Login as user "test-user" to account management.
|
||||
authenticateWithIdentityProvider(getIdentityProviderModel(), "test-user", false);
|
||||
changePasswordPage.realm("realm-with-broker");
|
||||
changePasswordPage.open();
|
||||
assertTrue(changePasswordPage.isCurrent());
|
||||
|
||||
// Assert changing password with old password "secret" as this is the password from federationProvider (See DummyUserFederationProvider)
|
||||
changePasswordPage.changePassword("new-password", "new-password");
|
||||
Assert.assertEquals("Please specify password.", accountUpdateProfilePage.getError());
|
||||
|
||||
changePasswordPage.changePassword("bad", "new-password", "new-password");
|
||||
Assert.assertEquals("Invalid existing password.", accountUpdateProfilePage.getError());
|
||||
|
||||
changePasswordPage.changePassword("secret", "new-password", "new-password");
|
||||
Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());
|
||||
|
||||
// Logout
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
|
||||
|
||||
// Login as user "test-user-noemail" .
|
||||
authenticateWithIdentityProvider(getIdentityProviderModel(), "test-user-noemail", false);
|
||||
changePasswordPage.open();
|
||||
assertTrue(changePasswordPage.isCurrent());
|
||||
|
||||
// Assert old password is not required as federationProvider doesn't have it for this user
|
||||
changePasswordPage.changePassword("new-password", "new-password");
|
||||
Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());
|
||||
|
||||
// Now it is required as it's set on model
|
||||
changePasswordPage.changePassword("new-password2", "new-password2");
|
||||
Assert.assertEquals("Please specify password.", accountUpdateProfilePage.getError());
|
||||
|
||||
changePasswordPage.changePassword("new-password", "new-password2", "new-password2");
|
||||
Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());
|
||||
|
||||
// Logout
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
} finally {
|
||||
|
||||
// remove dummy federation provider for this realm
|
||||
realm = getRealm();
|
||||
realm.removeUserFederationProvider(dummyModel);
|
||||
|
||||
brokerServerRule.stopSession(session, true);
|
||||
session = brokerServerRule.startSession();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -359,7 +359,7 @@ public class BruteForceTest {
|
|||
|
||||
loginPage.assertCurrent();
|
||||
String src = driver.getPageSource();
|
||||
Assert.assertEquals("Account is temporarily disabled, contact admin or try again later.", loginPage.getError());
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
events.expectLogin().session((String) null).error(Errors.USER_TEMPORARILY_DISABLED)
|
||||
.detail(Details.USERNAME, "test-user@localhost")
|
||||
.removeDetail(Details.CONSENT)
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.events.Details;
|
|||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.BrowserSecurityHeaders;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
@ -64,17 +65,13 @@ public class LoginTest {
|
|||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
UserCredentialModel creds = new UserCredentialModel();
|
||||
creds.setType(CredentialRepresentation.PASSWORD);
|
||||
creds.setValue("password");
|
||||
|
||||
UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
|
||||
user.setEmail("login@test.com");
|
||||
user.setEnabled(true);
|
||||
|
||||
userId = user.getId();
|
||||
|
||||
user.updateCredential(creds);
|
||||
user.updateCredential(UserCredentialModel.password("password"));
|
||||
|
||||
UserModel user2 = manager.getSession().users().addUser(appRealm, "login-test2");
|
||||
user2.setEmail("login2@test.com");
|
||||
|
@ -82,7 +79,7 @@ public class LoginTest {
|
|||
|
||||
user2Id = user2.getId();
|
||||
|
||||
user2.updateCredential(creds);
|
||||
user2.updateCredential(UserCredentialModel.password("password"));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -303,6 +300,26 @@ public class LoginTest {
|
|||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
// KEYCLOAK-2557
|
||||
public void loginUserWithEmailAsUsername() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
|
||||
UserModel user = session.users().addUser(session.realms().getRealmByName("test"), "login@test.com");
|
||||
user.setEnabled(true);
|
||||
user.updateCredential(UserCredentialModel.password("password"));
|
||||
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("login@test.com", "password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginSuccess() {
|
||||
loginPage.open();
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.testsuite.model;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ConcurrentTransactionsTest extends AbstractModelTest {
|
||||
|
||||
@Test
|
||||
public void persistClient() throws Exception {
|
||||
RealmModel realm = realmManager.createRealm("original");
|
||||
KeycloakSession session = realmManager.getSession();
|
||||
|
||||
ClientModel client = session.realms().addClient(realm, "client");
|
||||
client.setSecret("old");
|
||||
|
||||
String clientDBId = client.getId();
|
||||
|
||||
final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||
commit();
|
||||
|
||||
final CountDownLatch transactionsCounter = new CountDownLatch(2);
|
||||
final CountDownLatch readLatch = new CountDownLatch(1);
|
||||
final CountDownLatch updateLatch = new CountDownLatch(1);
|
||||
|
||||
Thread thread1 = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
try {
|
||||
// Wait until transaction in both threads started
|
||||
transactionsCounter.countDown();
|
||||
System.out.println("transaction1 started");
|
||||
transactionsCounter.await();
|
||||
|
||||
// Read client
|
||||
RealmModel realm = session.realms().getRealmByName("original");
|
||||
ClientModel client = session.realms().getClientByClientId("client", realm);
|
||||
System.out.println("transaction1: Read client finished");
|
||||
readLatch.countDown();
|
||||
|
||||
// Wait until thread2 updates client and commits
|
||||
updateLatch.await();
|
||||
System.out.println("transaction1: Going to read client again");
|
||||
|
||||
client = session.realms().getClientByClientId("client", realm);
|
||||
System.out.println("transaction1: secret: " + client.getSecret());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Thread thread2 = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
try {
|
||||
// Wait until transaction in both threads started
|
||||
transactionsCounter.countDown();
|
||||
System.out.println("transaction2 started");
|
||||
transactionsCounter.await();
|
||||
|
||||
|
||||
readLatch.await();
|
||||
System.out.println("transaction2: Going to update client secret");
|
||||
|
||||
RealmModel realm = session.realms().getRealmByName("original");
|
||||
ClientModel client = session.realms().getClientByClientId("client", realm);
|
||||
client.setSecret("new");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
System.out.println("transaction2: commited");
|
||||
updateLatch.countDown();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
|
||||
System.out.println("after thread join");
|
||||
|
||||
commit();
|
||||
|
||||
session = realmManager.getSession();
|
||||
|
||||
realm = session.realms().getRealmByName("original");
|
||||
ClientModel clientFromCache = session.realms().getClientById(clientDBId, realm);
|
||||
ClientModel clientFromDB = session.getProvider(RealmProvider.class).getClientById(clientDBId, realm);
|
||||
|
||||
System.out.println("SECRET FROM DB : " + clientFromDB.getSecret());
|
||||
System.out.println("SECRET FROM CACHE : " + clientFromCache.getSecret());
|
||||
|
||||
Assert.assertEquals("new", clientFromDB.getSecret());
|
||||
Assert.assertEquals("new", clientFromCache.getSecret());
|
||||
}
|
||||
|
||||
}
|
|
@ -29,8 +29,10 @@ import org.keycloak.events.Details;
|
|||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
|
@ -63,9 +65,24 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
user.setEmail("direct-login@localhost");
|
||||
user.setEnabled(true);
|
||||
|
||||
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
|
||||
|
||||
userId = user.getId();
|
||||
|
||||
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
|
||||
UserModel user2 = session.users().addUser(appRealm, "direct-login-otp");
|
||||
user2.setEnabled(true);
|
||||
|
||||
UserCredentialModel credentials = new UserCredentialModel();
|
||||
credentials.setType(CredentialRepresentation.TOTP);
|
||||
credentials.setValue("totpSecret");
|
||||
user2.updateCredential(credentials);
|
||||
|
||||
user2.setOtpEnabled(true);
|
||||
|
||||
session.users().updateCredential(appRealm, user2, UserCredentialModel.password("password"));
|
||||
|
||||
userId2 = user2.getId();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -83,6 +100,10 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
|
||||
private static String userId;
|
||||
|
||||
private static String userId2;
|
||||
|
||||
private TimeBasedOTP totp = new TimeBasedOTP();
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenUsername() throws Exception {
|
||||
grantAccessToken("direct-login", "resource-owner");
|
||||
|
@ -98,11 +119,57 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
grantAccessToken("direct-login", "resource-owner-public");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenWithTotp() throws Exception {
|
||||
grantAccessToken(userId2, "direct-login-otp", "resource-owner", totp.generateTOTP("totpSecret"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenMissingTotp() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "direct-login-otp", "password");
|
||||
|
||||
assertEquals(401, response.getStatusCode());
|
||||
|
||||
assertEquals("invalid_grant", response.getError());
|
||||
|
||||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session((String) null)
|
||||
.clearDetails()
|
||||
.error(Errors.INVALID_USER_CREDENTIALS)
|
||||
.user(userId2)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenInvalidTotp() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "direct-login-otp", "password", totp.generateTOTP("totpSecret2"));
|
||||
|
||||
assertEquals(401, response.getStatusCode());
|
||||
|
||||
assertEquals("invalid_grant", response.getError());
|
||||
|
||||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session((String) null)
|
||||
.clearDetails()
|
||||
.error(Errors.INVALID_USER_CREDENTIALS)
|
||||
.user(userId2)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
private void grantAccessToken(String login, String clientId) throws Exception {
|
||||
grantAccessToken(userId, login, clientId, null);
|
||||
}
|
||||
|
||||
private void grantAccessToken(String userId, String login, String clientId, String otp) throws Exception {
|
||||
oauth.clientId(clientId);
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password");
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password", otp);
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
|
|
|
@ -22,8 +22,13 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.util.Environment;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
|
@ -66,7 +71,28 @@ import static org.junit.Assert.assertEquals;
|
|||
*/
|
||||
public class SamlPicketlinkSPTest {
|
||||
|
||||
@ClassRule
|
||||
// This test is ignored in IBM JDK due to the IBM JDK bug, which is handled in Keycloak SP ( org.keycloak.saml.common.parsers.AbstractParser ) but not in Picketlink SP
|
||||
public static TestRule ignoreIBMJDK = new TestRule() {
|
||||
|
||||
@Override
|
||||
public Statement apply(final Statement base, final Description description) {
|
||||
return new Statement() {
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
if (Environment.IS_IBM_JAVA) {
|
||||
System.err.println("Ignore " + description.getDisplayName() + " because executing on IBM JDK");
|
||||
} else {
|
||||
base.evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
public static SamlKeycloakRule keycloakRule = new SamlKeycloakRule() {
|
||||
@Override
|
||||
public void initWars() {
|
||||
|
@ -97,6 +123,12 @@ public class SamlPicketlinkSPTest {
|
|||
}
|
||||
};
|
||||
|
||||
@ClassRule
|
||||
public static TestRule chain = RuleChain
|
||||
.outerRule(ignoreIBMJDK)
|
||||
.around(keycloakRule);
|
||||
|
||||
|
||||
public static class SamlSPFacade extends HttpServlet {
|
||||
public static String samlResponse;
|
||||
public static String RELAY_STATE = "http://test.com/foo/bar";
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="password" name="password" autofocus>
|
||||
<input type="password" class="form-control" id="password" name="password" autofocus autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="password-new" name="password-new">
|
||||
<input type="password" class="form-control" id="password-new" name="password-new" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="password-confirm" name="password-confirm">
|
||||
<input type="password" class="form-control" id="password-confirm" name="password-confirm" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="text" class="form-control" id="totp" name="totp" autocomplete="off" autofocus>
|
||||
<input type="text" class="form-control" id="totp" name="totp" autocomplete="off" autofocus autocomplete="off">
|
||||
<input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,901 +0,0 @@
|
|||
# Common messages
|
||||
enabled=de Enabled
|
||||
name=de Name
|
||||
displayName=de Display name
|
||||
displayNameHtml=de HTML Display name
|
||||
save=de Save
|
||||
cancel=de Cancel
|
||||
onText=AN
|
||||
offText=AUS
|
||||
client=de Client
|
||||
clients=de Clients
|
||||
clear=de Clear
|
||||
selectOne=de Select One...
|
||||
|
||||
true=de True
|
||||
false=de False
|
||||
|
||||
|
||||
# Realm settings
|
||||
realm-detail.enabled.tooltip=de Users and clients can only access a realm if it's enabled
|
||||
registrationAllowed=de User registration
|
||||
registrationAllowed.tooltip=de Enable/disable the registration page. A link for registration will show on login page too.
|
||||
registrationEmailAsUsername=de Email as username
|
||||
registrationEmailAsUsername.tooltip=de If enabled then username field is hidden from registration form and email is used as username for new user.
|
||||
editUsernameAllowed=de Edit username
|
||||
editUsernameAllowed.tooltip=de If enabled, the username field is editable, readonly otherwise.
|
||||
resetPasswordAllowed=de Forgot password
|
||||
resetPasswordAllowed.tooltip=de Show a link on login page for user to click on when they have forgotten their credentials.
|
||||
rememberMe=de Remember Me
|
||||
rememberMe.tooltip=de Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
|
||||
verifyEmail=de Verify email
|
||||
verifyEmail.tooltip=de Require the user to verify their email address the first time they login.
|
||||
sslRequired=de Require SSL
|
||||
sslRequired.option.all=de all requests
|
||||
sslRequired.option.external=de external requests
|
||||
sslRequired.option.none=de none
|
||||
sslRequired.tooltip=de 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.
|
||||
publicKey=de Public key
|
||||
gen-new-keys=de Generate new keys
|
||||
certificate=de Certificate
|
||||
host=de Host
|
||||
smtp-host=de SMTP Host
|
||||
port=de Port
|
||||
smtp-port=de SMTP Port (defaults to 25)
|
||||
from=de From
|
||||
sender-email-addr=de Sender Email Address
|
||||
enable-ssl=de Enable SSL
|
||||
enable-start-tls=de Enable StartTLS
|
||||
enable-auth=de Enable Authentication
|
||||
username=de Username
|
||||
login-username=de Login Username
|
||||
password=de Password
|
||||
login-password=de Login Password
|
||||
login-theme=de Login Theme
|
||||
login-theme.tooltip=de Select theme for login, TOTP, grant, registration, and forgot password pages.
|
||||
account-theme=de Account Theme
|
||||
account-theme.tooltip=de Select theme for user account management pages.
|
||||
admin-console-theme=de Admin Console Theme
|
||||
select-theme-admin-console=de Select theme for admin console.
|
||||
email-theme=de Email Theme
|
||||
select-theme-email=de Select theme for emails that are sent by the server.
|
||||
i18n-enabled=de Internationalization Enabled
|
||||
supported-locales=de Supported Locales
|
||||
supported-locales.placeholder=de Type a locale and enter
|
||||
default-locale=de Default Locale
|
||||
realm-cache-clear=de Realm Cache
|
||||
realm-cache-clear.tooltip=de Clears all entries from the realm cache (this will clear entries for all realms)
|
||||
user-cache-clear=de User Cache
|
||||
user-cache-clear.tooltip=de Clears all entries from the user cache (this will clear entries for all realms)
|
||||
revoke-refresh-token=de Revoke Refresh Token
|
||||
revoke-refresh-token.tooltip=de If enabled refresh tokens can only be used once. Otherwise refresh tokens are not revoked when used and can be used multiple times.
|
||||
sso-session-idle=de SSO Session Idle
|
||||
seconds=de Seconds
|
||||
minutes=de Minutes
|
||||
hours=de Hours
|
||||
days=de Days
|
||||
sso-session-max=de SSO Session Max
|
||||
sso-session-idle.tooltip=de Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
|
||||
sso-session-max.tooltip=de Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.
|
||||
offline-session-idle=de Offline Session Idle
|
||||
offline-session-idle.tooltip=de Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
|
||||
access-token-lifespan=de Access Token Lifespan
|
||||
access-token-lifespan.tooltip=de Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
|
||||
access-token-lifespan-for-implicit-flow=de Access Token Lifespan For Implicit Flow
|
||||
access-token-lifespan-for-implicit-flow.tooltip=de Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is separate timeout different to 'Access Token Lifespan'.
|
||||
client-login-timeout=de Client login timeout
|
||||
client-login-timeout.tooltip=de Max time an client has to finish the access token protocol. This should normally be 1 minute.
|
||||
login-timeout=de Login timeout
|
||||
login-timeout.tooltip=de Max time a user has to complete a login. This is recommended to be relatively long. 30 minutes or more.
|
||||
login-action-timeout=de Login action timeout
|
||||
login-action-timeout.tooltip=de Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more.
|
||||
headers=de Headers
|
||||
brute-force-detection=de Brute Force Detection
|
||||
x-frame-options=de X-Frame-Options
|
||||
click-label-for-info=de Click on label link for more information. The default value prevents pages from being included via non-origin iframes.
|
||||
content-sec-policy=de Content-Security-Policy
|
||||
max-login-failures=de Max Login Failures
|
||||
max-login-failures.tooltip=de How many failures before wait is triggered.
|
||||
wait-increment=de Wait Increment
|
||||
wait-increment.tooltip=de When failure threshold has been met, how much time should the user be locked out?
|
||||
quick-login-check-millis=de Quick Login Check Milli Seconds
|
||||
quick-login-check-millis.tooltip=de If a failure happens concurrently too quickly, lock out the user.
|
||||
min-quick-login-wait=de Minimum Quick Login Wait
|
||||
min-quick-login-wait.tooltip=de How long to wait after a quick login failure.
|
||||
max-wait=de Max Wait
|
||||
max-wait.tooltip=de Max time a user will be locked out.
|
||||
failure-reset-time=de Failure Reset Time
|
||||
failure-reset-time.tooltip=de When will failure count be reset?
|
||||
realm-tab-login=de Login
|
||||
realm-tab-keys=de Keys
|
||||
realm-tab-email=de Email
|
||||
realm-tab-themes=de Themes
|
||||
realm-tab-cache=de Cache
|
||||
realm-tab-tokens=de Tokens
|
||||
realm-tab-client-initial-access=de Initial Access Tokens
|
||||
realm-tab-security-defenses=de Security Defenses
|
||||
realm-tab-general=de General
|
||||
add-realm=de Add realm
|
||||
|
||||
#Session settings
|
||||
realm-sessions=de Realm Sessions
|
||||
revocation=de Revocation
|
||||
logout-all=de Logout all
|
||||
active-sessions=de Active Sessions
|
||||
sessions=de Sessions
|
||||
not-before=de Not Before
|
||||
not-before.tooltip=de Revoke any tokens issued before this date.
|
||||
set-to-now=de Set to now
|
||||
push=de Push
|
||||
push.tooltip=de For every client that has an admin URL, notify them of the new revocation policy.
|
||||
|
||||
#Protocol Mapper
|
||||
usermodel.prop.label=de Property
|
||||
usermodel.prop.tooltip=de Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method.
|
||||
usermodel.attr.label=de User Attribute
|
||||
usermodel.attr.tooltip=de Name of stored user attribute which is the name of an attribute within the UserModel.attribute map.
|
||||
userSession.modelNote.label=de User Session Note
|
||||
userSession.modelNote.tooltip=de Name of stored user session note within the UserSessionModel.note map.
|
||||
multivalued.label=de Multivalued
|
||||
multivalued.tooltip=de Indicates if attribute supports multiple values. If true, then the list of all values of this attribute will be set as claim. If false, then just first value will be set as claim
|
||||
selectRole.label=de Select Role
|
||||
selectRole.tooltip=de Enter role in the textbox to the left, or click this button to browse and select the role you want
|
||||
tokenClaimName.label=de Token Claim Name
|
||||
tokenClaimName.tooltip=de Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.
|
||||
jsonType.label=de Claim JSON Type
|
||||
jsonType.tooltip=de JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.
|
||||
includeInIdToken.label=de Add to ID token
|
||||
includeInIdToken.tooltip=de Should the claim be added to the ID token?
|
||||
includeInAccessToken.label=de Add to access token
|
||||
includeInAccessToken.tooltip=de Should the claim be added to the access token?
|
||||
|
||||
|
||||
# client details
|
||||
clients.tooltip=de Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles.
|
||||
search.placeholder=de Search...
|
||||
create=de Create
|
||||
import=de Import
|
||||
client-id=de Client ID
|
||||
base-url=de Base URL
|
||||
actions=de Actions
|
||||
not-defined=de Not defined
|
||||
edit=de Edit
|
||||
delete=de Delete
|
||||
no-results=de No results
|
||||
no-clients-available=de No clients available
|
||||
add-client=de Add Client
|
||||
select-file=de Select file
|
||||
view-details=de View details
|
||||
clear-import=de Clear import
|
||||
client-id.tooltip=de Specifies ID referenced in URI and tokens. For example 'my-client'
|
||||
client.name.tooltip=de Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client}
|
||||
client.enabled.tooltip=de Disabled clients cannot initiate a login or have obtain access tokens.
|
||||
consent-required=de Consent Required
|
||||
consent-required.tooltip=de If enabled users have to consent to client access.
|
||||
direct-grants-only=de Direct Grants Only
|
||||
direct-grants-only.tooltip=de When enabled, client can only obtain grants from grant REST API.
|
||||
client-protocol=de Client Protocol
|
||||
client-protocol.tooltip=de 'OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.
|
||||
access-type=de Access Type
|
||||
access-type.tooltip=de 'Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
|
||||
standard-flow-enabled=de Standard Flow Enabled
|
||||
standard-flow-enabled.tooltip=de This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.
|
||||
implicit-flow-enabled=de Implicit Flow Enabled
|
||||
implicit-flow-enabled.tooltip=de This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.
|
||||
direct-access-grants-enabled=de Direct Access Grants Enabled
|
||||
direct-access-grants-enabled.tooltip=de This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.
|
||||
service-accounts-enabled=de Service Accounts Enabled
|
||||
service-accounts-enabled.tooltip=de Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.
|
||||
include-authnstatement=de Include AuthnStatement
|
||||
include-authnstatement.tooltip=de Should a statement specifying the method and timestamp be included in login responses?
|
||||
sign-documents=de Sign Documents
|
||||
sign-documents.tooltip=de Should SAML documents be signed by the realm?
|
||||
sign-assertions=de Sign Assertions
|
||||
sign-assertions.tooltip=de Should assertions inside SAML documents be signed? This setting isn't needed if document is already being signed.
|
||||
signature-algorithm=de Signature Algorithm
|
||||
signature-algorithm.tooltip=de The signature algorithm to use to sign documents.
|
||||
canonicalization-method=de Canonicalization Method
|
||||
canonicalization-method.tooltip=de Canonicalization Method for XML signatures.
|
||||
encrypt-assertions=de Encrypt Assertions
|
||||
encrypt-assertions.tooltip=de Should SAML assertions be encrypted with client's public key using AES?
|
||||
client-signature-required=de Client Signature Required
|
||||
client-signature-required.tooltip=de Will the client sign their saml requests and responses? And should they be validated?
|
||||
force-post-binding=de Force POST Binding
|
||||
force-post-binding.tooltip=de Always use POST binding for responses.
|
||||
front-channel-logout=de Front Channel Logout
|
||||
front-channel-logout.tooltip=de When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout.
|
||||
force-name-id-format=de Force Name ID Format
|
||||
force-name-id-format.tooltip=de Ignore requested NameID subject format and use admin console configured one.
|
||||
name-id-format=de Name ID Format
|
||||
name-id-format.tooltip=de The name ID format to use for the subject.
|
||||
root-url=de Root URL
|
||||
root-url.tooltip=de Root URL appended to relative URLs
|
||||
valid-redirect-uris=de Valid Redirect URIs
|
||||
valid-redirect-uris.tooltip=de Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed i.e. 'http://example.com/*'. Relative path can be specified too i.e. /my/relative/path/*. Relative paths will generate a redirect URI using the request's host and port. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request.
|
||||
base-url.tooltip=de Default URL to use when the auth server needs to redirect or link back to the client.
|
||||
admin-url=de Admin URL
|
||||
admin-url.tooltip=de URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other adminstrative tasks. Usually this is set to the base URL of the client.
|
||||
master-saml-processing-url=de Master SAML Processing URL
|
||||
master-saml-processing-url.tooltip=de If configured, this URL will be used for every binding to both the SP's Assertion Consumer and Single Logout Services. This can be individually overiden for each binding and service in the Fine Grain SAML Endpoint Configuration.
|
||||
idp-sso-url-ref=de IDP Initiated SSO URL Name
|
||||
idp-sso-url-ref.tooltip=de URL fragment name to reference client when you want to do IDP Initiated SSO. Leaving this empty will disable IDP Initiated SSO. The URL you will reference from your browser will be: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
|
||||
idp-sso-relay-state=de IDP Initiated SSO Relay State
|
||||
idp-sso-relay-state.tooltip=de Relay state you want to send with SAML request when you want to do IDP Initiated SSO.
|
||||
web-origins=de Web Origins
|
||||
web-origins.tooltip=de Allowed CORS origins. To permit all origins of Valid Redirect URIs add '+'. To permit all origins add '*'.
|
||||
fine-saml-endpoint-conf=de Fine Grain SAML Endpoint Configuration
|
||||
fine-saml-endpoint-conf.tooltip=de Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service.
|
||||
assertion-consumer-post-binding-url=de Assertion Consumer Service POST Binding URL
|
||||
assertion-consumer-post-binding-url.tooltip=de SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
|
||||
assertion-consumer-redirect-binding-url=de Assertion Consumer Service Redirect Binding URL
|
||||
assertion-consumer-redirect-binding-url.tooltip=de SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
|
||||
logout-service-binding-post-url=de Logout Service POST Binding URL
|
||||
logout-service-binding-post-url.tooltip=de SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding
|
||||
logout-service-redir-binding-url=de Logout Service Redirect Binding URL
|
||||
logout-service-redir-binding-url.tooltip=de SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding.
|
||||
|
||||
# client import
|
||||
import-client=de Import Client
|
||||
format-option=de Format Option
|
||||
select-format=de Select a Format
|
||||
import-file=de Import File
|
||||
|
||||
# client tabs
|
||||
settings=de Settings
|
||||
credentials=de Credentials
|
||||
saml-keys=de SAML Keys
|
||||
roles=de Roles
|
||||
mappers=de Mappers
|
||||
mappers.tooltip=de Protocol mappers perform transformation on tokens and documents. They an do things like map user data into protocol claims, or just transform any requests going between the client and auth server.
|
||||
scope=de Scope
|
||||
scope.tooltip=de Scope mappings allow you to restrict which user role mappings are included within the access token requested by the client.
|
||||
sessions.tooltip=de View active sessions for this client. Allows you to see which users are active and when they logged in.
|
||||
offline-access=de Offline Access
|
||||
offline-access.tooltip=de View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it. To revoke all tokens for the client, go to Revocation tab and set not before value to now.
|
||||
clustering=de Clustering
|
||||
installation=de Installation
|
||||
installation.tooltip=de Helper utility for generating various client adapter configuration formats which you can download or cut and paste to configure your clients.
|
||||
service-account-roles=de Service Account Roles
|
||||
service-account-roles.tooltip=de Allows you to authenticate role mappings for the service account dedicated to this client.
|
||||
|
||||
# client credentials
|
||||
client-authenticator=de Client Authenticator
|
||||
client-authenticator.tooltip=de Client Authenticator used for authentication this client against Keycloak server
|
||||
certificate.tooltip=de Client Certificate for validate JWT issued by client and signed by Client private key from your keystore.
|
||||
no-client-certificate-configured=de No client certificate configured
|
||||
gen-new-keys-and-cert=de Generate new keys and certificate
|
||||
import-certificate=de Import Certificate
|
||||
gen-client-private-key=de Generate Client Private Key
|
||||
generate-private-key=de Generate Private Key
|
||||
archive-format=de Archive Format
|
||||
archive-format.tooltip=de Java keystore or PKCS12 archive format.
|
||||
key-alias=de Key Alias
|
||||
key-alias.tooltip=de Archive alias for your private key and certificate.
|
||||
key-password=de Key Password
|
||||
key-password.tooltip=de Password to access the private key in the archive
|
||||
store-password=de Store Password
|
||||
store-password.tooltip=de Password to access the archive itself
|
||||
generate-and-download=de Generate and Download
|
||||
client-certificate-import=de Client Certificate Import
|
||||
import-client-certificate=de Import Client Certificate
|
||||
jwt-import.key-alias.tooltip=de Archive alias for your certificate.
|
||||
secret=de Secret
|
||||
regenerate-secret=de Regenerate Secret
|
||||
registrationAccessToken=de Registration access token
|
||||
registrationAccessToken.regenerate=de Regenerate registration access token
|
||||
registrationAccessToken.tooltip=de The registration access token provides access for clients to the client registration service.
|
||||
add-role=de Add Role
|
||||
role-name=de Role Name
|
||||
composite=de Composite
|
||||
description=de Description
|
||||
no-client-roles-available=de No client roles available
|
||||
scope-param-required=de Scope Param Required
|
||||
scope-param-required.tooltip=de This role will only be granted if scope parameter with role name is used during authorization/token request.
|
||||
composite-roles=de Composite Roles
|
||||
composite-roles.tooltip=de When this role is (un)assigned to a user any role associated with it will be (un)assigned implicitly.
|
||||
realm-roles=de Realm Roles
|
||||
available-roles=de Available Roles
|
||||
add-selected=de Add selected
|
||||
associated-roles=de Associated Roles
|
||||
composite.associated-realm-roles.tooltip=de Realm level roles associated with this composite role.
|
||||
composite.available-realm-roles.tooltip=de Realm level roles associated with this composite role.
|
||||
remove-selected=de Remove selected
|
||||
client-roles=de Client Roles
|
||||
select-client-to-view-roles=de Select client to view roles for client
|
||||
available-roles.tooltip=de Roles from this client that you can associate to this composite role.
|
||||
client.associated-roles.tooltip=de Client roles associated with this composite role.
|
||||
add-builtin=de Add Builtin
|
||||
category=de Category
|
||||
type=de Type
|
||||
no-mappers-available=de No mappers available
|
||||
add-builtin-protocol-mappers=de Add Builtin Protocol Mappers
|
||||
add-builtin-protocol-mapper=de Add Builtin Protocol Mapper
|
||||
scope-mappings=de Scope Mappings
|
||||
full-scope-allowed=de Full Scope Allowed
|
||||
full-scope-allowed.tooltip=de Allows you to disable all restrictions.
|
||||
scope.available-roles.tooltip=de Realm level roles that can be assigned to scope.
|
||||
assigned-roles=de Assigned Roles
|
||||
assigned-roles.tooltip=de Realm level roles assigned to scope.
|
||||
effective-roles=de Effective Roles
|
||||
realm.effective-roles.tooltip=de Assigned realm level roles that may have been inherited from a composite role.
|
||||
select-client-roles.tooltip=de Select client to view roles for client
|
||||
assign.available-roles.tooltip=de Client roles available to be assigned.
|
||||
client.assigned-roles.tooltip=de Assigned client roles.
|
||||
client.effective-roles.tooltip=de Assigned client roles that may have been inherited from a composite role.
|
||||
basic-configuration=de Basic configuration
|
||||
node-reregistration-timeout=de Node Re-registration Timeout
|
||||
node-reregistration-timeout.tooltip=de Interval to specify max time for registered clients cluster nodes to re-register. If cluster node won't send re-registration request to Keycloak within this time, it will be unregistered from Keycloak
|
||||
registered-cluster-nodes=de Registered cluster nodes
|
||||
register-node-manually=de Register node manually
|
||||
test-cluster-availability=de Test cluster availability
|
||||
last-registration=de Last registration
|
||||
node-host=de Node host
|
||||
no-registered-cluster-nodes=de No registered cluster nodes available
|
||||
cluster-nodes=de Cluster Nodes
|
||||
add-node=de Add Node
|
||||
active-sessions.tooltip=de Total number of active user sessions for this client.
|
||||
show-sessions=de Show Sessions
|
||||
show-sessions.tooltip=de Warning, this is a potentially expensive operation depending on number of active sessions.
|
||||
user=de User
|
||||
from-ip=de From IP
|
||||
session-start=de Session Start
|
||||
first-page=de First Page
|
||||
previous-page=de Previous Page
|
||||
next-page=de Next Page
|
||||
client-revoke.not-before.tooltip=de Revoke any tokens issued before this date for this client.
|
||||
client-revoke.push.tooltip=de If admin URL is configured for this client, push this policy to that client.
|
||||
select-a-format=de Select a Format
|
||||
download=de Download
|
||||
offline-tokens=de Offline Tokens
|
||||
offline-tokens.tooltip=de Total number of offline tokens for this client.
|
||||
show-offline-tokens=de Show Offline Tokens
|
||||
show-offline-tokens.tooltip=de Warning, this is a potentially expensive operation depending on number of offline tokens.
|
||||
token-issued=de Token Issued
|
||||
last-access=de Last Access
|
||||
last-refresh=de Last Refresh
|
||||
key-export=de Key Export
|
||||
key-import=de Key Import
|
||||
export-saml-key=de Export SAML Key
|
||||
import-saml-key=de Import SAML Key
|
||||
realm-certificate-alias=de Realm Certificate Alias
|
||||
realm-certificate-alias.tooltip=de Realm certificate is stored in archive too. This is the alias to it.
|
||||
signing-key=de Signing Key
|
||||
saml-signing-key=de SAML Signing Key.
|
||||
private-key=de Private Key
|
||||
generate-new-keys=de Generate new keys
|
||||
export=de Export
|
||||
encryption-key=de Encryption Key
|
||||
saml-encryption-key.tooltip=de SAML Encryption Key.
|
||||
service-accounts=de Service Accounts
|
||||
service-account.available-roles.tooltip=de Realm level roles that can be assigned to service account.
|
||||
service-account.assigned-roles.tooltip=de Realm level roles assigned to service account.
|
||||
service-account-is-not-enabled-for=de Service account is not enabled for {{client}}
|
||||
create-protocol-mappers=de Create Protocol Mappers
|
||||
create-protocol-mapper=de Create Protocol Mapper
|
||||
protocol=de Protocol
|
||||
protocol.tooltip=de Protocol...
|
||||
id=de ID
|
||||
mapper.name.tooltip=de Name of the mapper.
|
||||
mapper.consent-required.tooltip=de When granting temporary access, must the user consent to providing this data to the client?
|
||||
consent-text=de Consent Text
|
||||
consent-text.tooltip=de Text to display on consent page.
|
||||
mapper-type=de Mapper Type
|
||||
mapper-type.tooltip=de Type of the mapper
|
||||
select-role=de Select role
|
||||
select-role.tooltip=de Enter role in the textbox to the left, or click this button to browse and select the role you want.
|
||||
|
||||
# realm identity providers
|
||||
identity-providers=de Identity Providers
|
||||
table-of-identity-providers=de Table of identity providers
|
||||
add-provider.placeholder=de Add provider...
|
||||
provider=de Provider
|
||||
gui-order=de GUI order
|
||||
first-broker-login-flow=de First Login Flow
|
||||
post-broker-login-flow=de Post Login Flow
|
||||
redirect-uri=de Redirect URI
|
||||
redirect-uri.tooltip=de The redirect uri to use when configuring the identity provider.
|
||||
alias=de Alias
|
||||
identity-provider.alias.tooltip=de The alias uniquely identifies an identity provider and it is also used to build the redirect uri.
|
||||
identity-provider.enabled.tooltip=de Enable/disable this identity provider.
|
||||
authenticate-by-default=de Authenticate by Default
|
||||
identity-provider.authenticate-by-default.tooltip=de Indicates if this provider should be tried by default for authentication even before displaying login screen.
|
||||
store-tokens=de Store Tokens
|
||||
identity-provider.store-tokens.tooltip=de Enable/disable if tokens must be stored after authenticating users.
|
||||
stored-tokens-readable=de Stored Tokens Readable
|
||||
identity-provider.stored-tokens-readable.tooltip=de Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.
|
||||
update-profile-on-first-login=de Update Profile on First Login
|
||||
on=de On
|
||||
on-missing-info=de On missing info
|
||||
off=de Off
|
||||
update-profile-on-first-login.tooltip=de Define conditions under which a user has to update their profile during first-time login.
|
||||
trust-email=de Trust Email
|
||||
trust-email.tooltip=de If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
|
||||
gui-order.tooltip=de Number defining order of the provider in GUI (eg. on Login page).
|
||||
first-broker-login-flow.tooltip=de Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
|
||||
post-broker-login-flow.tooltip=de Alias of authentication flow, which is triggered after each login with this identity provider. Useful if you want additional verification of each user authenticated with this identity provider (for example OTP). Leave this empty if you don't want any additional authenticators to be triggered after login with this identity provider. Also note, that authenticator implementations must assume that user is already set in ClientSession as identity provider already set it.
|
||||
openid-connect-config=de OpenID Connect Config
|
||||
openid-connect-config.tooltip=de OIDC SP and external IDP configuration.
|
||||
authorization-url=de Authorization URL
|
||||
authorization-url.tooltip=de The Authorization Url.
|
||||
token-url=de Token URL
|
||||
token-url.tooltip=de The Token URL.
|
||||
logout-url=de Logout URL
|
||||
identity-provider.logout-url.tooltip=de End session endpoint to use to logout user from external IDP.
|
||||
backchannel-logout=de Backchannel Logout
|
||||
backchannel-logout.tooltip=de Does the external IDP support backchannel logout?
|
||||
user-info-url=de User Info URL
|
||||
user-info-url.tooltip=de The User Info Url. This is optional.
|
||||
identity-provider.client-id.tooltip=de The client or client identifier registered within the identity provider.
|
||||
client-secret=de Client Secret
|
||||
show-secret=de Show secret
|
||||
hide-secret=de Hide secret
|
||||
client-secret.tooltip=de The client or client secret registered within the identity provider.
|
||||
issuer=de Issuer
|
||||
issuer.tooltip=de The issuer identifier for the issuer of the response. If not provided, no validation will be performed.
|
||||
default-scopes=de Default Scopes
|
||||
identity-provider.default-scopes.tooltip=de The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'.
|
||||
prompt=de Prompt
|
||||
unspecified.option=de unspecified
|
||||
none.option=de none
|
||||
consent.option=de consent
|
||||
login.option=de login
|
||||
select-account.option=de select_account
|
||||
prompt.tooltip=de Specifies whether the Authorization Server prompts the End-User for reauthentication and consent.
|
||||
validate-signatures=de Validate Signatures
|
||||
identity-provider.validate-signatures.tooltip=de Enable/disable signature validation of external IDP signatures.
|
||||
validating-public-key=de Validating Public Key
|
||||
identity-provider.validating-public-key.tooltip=de The public key in PEM format that must be used to verify external IDP signatures.
|
||||
import-external-idp-config=de Import External IDP Config
|
||||
import-external-idp-config.tooltip=de Allows you to load external IDP metadata from a config file or to download it from a URL.
|
||||
import-from-url=de Import from URL
|
||||
identity-provider.import-from-url.tooltip=de Import metadata from a remote IDP discovery descriptor.
|
||||
import-from-file=de Import from file
|
||||
identity-provider.import-from-file.tooltip=de Import metadata from a downloaded IDP discovery descriptor.
|
||||
saml-config=de SAML Config
|
||||
identity-provider.saml-config.tooltip=de SAML SP and external IDP configuration.
|
||||
single-signon-service-url=de Single Sign-On Service URL
|
||||
saml.single-signon-service-url.tooltip=de The Url that must be used to send authentication requests (SAML AuthnRequest).
|
||||
single-logout-service-url=de Single Logout Service URL
|
||||
saml.single-logout-service-url.tooltip=de The Url that must be used to send logout requests.
|
||||
nameid-policy-format=de NameID Policy Format
|
||||
nameid-policy-format.tooltip=de Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
|
||||
http-post-binding-response=de HTTP-POST Binding Response
|
||||
http-post-binding-response.tooltip=de Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.
|
||||
http-post-binding-for-authn-request=de HTTP-POST Binding for AuthnRequest
|
||||
http-post-binding-for-authn-request.tooltip=de Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.
|
||||
want-authn-requests-signed=de Want AuthnRequests Signed
|
||||
want-authn-requests-signed.tooltip=de Indicates whether the identity provider expects signed a AuthnRequest.
|
||||
force-authentication=de Force Authentication
|
||||
identity-provider.force-authentication.tooltip=de Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.
|
||||
validate-signature=de Validate Signature
|
||||
saml.validate-signature.tooltip=de Enable/disable signature validation of SAML responses.
|
||||
validating-x509-certificate=de Validating X509 Certificate
|
||||
validating-x509-certificate.tooltip=de The certificate in PEM format that must be used to check for signatures.
|
||||
saml.import-from-url.tooltip=de Import metadata from a remote IDP SAML entity descriptor.
|
||||
social.client-id.tooltip=de The client identifier registered with the identity provider.
|
||||
social.client-secret.tooltip=de The client secret registered with the identity provider.
|
||||
social.default-scopes.tooltip=de The scopes to be sent when asking for authorization. See documentation for possible values, separator and default value'.
|
||||
key=de Key
|
||||
stackoverflow.key.tooltip=de The Key obtained from Stack Overflow client registration.
|
||||
|
||||
realms=de Realms
|
||||
realm=de Realm
|
||||
|
||||
identity-provider-mappers=de Identity Provider Mappers
|
||||
create-identity-provider-mapper=de Create Identity Provider Mapper
|
||||
add-identity-provider-mapper=de Add Identity Provider Mapper
|
||||
client.description.tooltip=de Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description}
|
||||
|
||||
expires=de Expires
|
||||
expiration=de Expiration
|
||||
expiration.tooltip=de Specifies how long the token should be valid
|
||||
count=de Count
|
||||
count.tooltip=de Specifies how many clients can be created using the token
|
||||
remainingCount=de Remaining Count
|
||||
created=de Created
|
||||
back=de Back
|
||||
initial-access-tokens=de Initial Access Tokens
|
||||
add-initial-access-tokens=de Add Initial Access Token
|
||||
initial-access-token=de Initial Access Token
|
||||
initial-access.copyPaste.tooltip=de Copy/paste the initial access token before navigating away from this page as it's not posible to retrieve later
|
||||
continue=de Continue
|
||||
initial-access-token.confirm.title=de Copy Initial Access Token
|
||||
initial-access-token.confirm.text=de Please copy and paste the initial access token before confirming as it can't be retrieved later
|
||||
|
||||
client-templates=de Client Templates
|
||||
client-templates.tooltip=de Client templates allow you to define common configuration that is shared between multiple clients
|
||||
|
||||
groups=de Groups
|
||||
|
||||
group.add-selected.tooltip=de Realm roles that can be assigned to the group.
|
||||
group.assigned-roles.tooltip=de Realm roles mapped to the group
|
||||
group.effective-roles.tooltip=de All realm role mappings. Some roles here might be inherited from a mapped composite role.
|
||||
group.available-roles.tooltip=de Assignable roles from this client.
|
||||
group.assigned-roles-client.tooltip=de Role mappings for this client.
|
||||
group.effective-roles-client.tooltip=de Role mappings for this client. Some roles here might be inherited from a mapped composite role.
|
||||
|
||||
default-roles=de Default Roles
|
||||
no-realm-roles-available=de No realm roles available
|
||||
|
||||
users=de Users
|
||||
user.add-selected.tooltip=de Realm roles that can be assigned to the user.
|
||||
user.assigned-roles.tooltip=de Realm roles mapped to the user
|
||||
user.effective-roles.tooltip=de All realm role mappings. Some roles here might be inherited from a mapped composite role.
|
||||
user.available-roles.tooltip=de Assignable roles from this client.
|
||||
user.assigned-roles-client.tooltip=de Role mappings for this client.
|
||||
user.effective-roles-client.tooltip=de Role mappings for this client. Some roles here might be inherited from a mapped composite role.
|
||||
default.available-roles.tooltip=de Realm level roles that can be assigned.
|
||||
realm-default-roles=de Realm Default Roles
|
||||
realm-default-roles.tooltip=de Realm level roles assigned to new users.
|
||||
default.available-roles-client.tooltip=de Roles from this client that are assignable as a default.
|
||||
client-default-roles=de Client Default Roles
|
||||
client-default-roles.tooltip=de Roles from this client assigned as a default role.
|
||||
composite.available-roles.tooltip=de Realm level roles associated with this composite role.
|
||||
composite.associated-roles.tooltip=de Realm level roles associated with this composite role.
|
||||
composite.available-roles-client.tooltip=de Roles from this client that you can associate to this composite role.
|
||||
composite.associated-roles-client.tooltip=de Client roles associated with this composite role.
|
||||
partial-import=de Partial Import
|
||||
|
||||
file=de File
|
||||
import-from-realm=de Import from realm
|
||||
import-users=de Import users
|
||||
import-clients=de Import clients
|
||||
import-identity-providers=de Import identity providers
|
||||
import-realm-roles=de Import realm roles
|
||||
import-client-roles=de Import client roles
|
||||
if-resource-exists=de If a resource exists
|
||||
fail=de Fail
|
||||
skip=de Skip
|
||||
overwrite=de Overwrite
|
||||
if-resource-exists.tooltip=de Specify what should be done if you try to import a resource that already exists.
|
||||
|
||||
action=de Action
|
||||
role-selector=de Role Selector
|
||||
realm-roles.tooltip=de Realm roles that can be selected.
|
||||
|
||||
select-a-role=de Select a role
|
||||
select-realm-role=de Select realm role
|
||||
client-roles.tooltip=de Client roles that can be selected.
|
||||
select-client-role=de Select client role
|
||||
|
||||
client-template=de Client Template
|
||||
client-template.tooltip=de Client template this client inherits configuration from
|
||||
client-saml-endpoint=de Client SAML Endpoint
|
||||
add-client-template=de Add client template
|
||||
|
||||
manage=de Manage
|
||||
authentication=de Authentication
|
||||
user-federation=de User Federation
|
||||
events=de Events
|
||||
realm-settings=de Realm Settings
|
||||
configure=de Configure
|
||||
select-realm=de Select realm
|
||||
add=de Add
|
||||
|
||||
client-template.name.tooltip=de Name of the client template. Must be unique in the realm
|
||||
client-template.description.tooltip=de Description of the client template
|
||||
client-template.protocol.tooltip=de Which SSO protocol configuration is being supplied by this client template
|
||||
|
||||
add-user-federation-provider=de Add user federation provider
|
||||
required-settings=de Required Settings
|
||||
provider-id=de Provider ID
|
||||
console-display-name=de Console Display Name
|
||||
console-display-name.tooltip=de Display name of provider when linked in admin console.
|
||||
priority=de Priority
|
||||
priority.tooltip=de Priority of provider when doing a user lookup. Lowest first.
|
||||
sync-settings=de Sync Settings
|
||||
periodic-full-sync=de Periodic Full Sync
|
||||
periodic-full-sync.tooltip=de Does periodic full synchronization of provider users to Keycloak should be enabled or not
|
||||
full-sync-period=de Full Sync Period
|
||||
full-sync-period.tooltip=de Period for full synchronization in seconds
|
||||
periodic-changed-users-sync=de Periodic Changed Users Sync
|
||||
periodic-changed-users-sync.tooltip=de Does periodic synchronization of changed or newly created provider users to Keycloak should be enabled or not
|
||||
changed-users-sync-period=de Changed Users Sync Period
|
||||
changed-users-sync-period.tooltip=de Period for synchronization of changed or newly created provider users in seconds
|
||||
synchronize-changed-users=de Synchronize changed users
|
||||
synchronize-all-users=de Synchronize all users
|
||||
kerberos-realm=de Kerberos Realm
|
||||
kerberos-realm.tooltip=de Name of kerberos realm. For example FOO.ORG
|
||||
server-principal=de Server Principal
|
||||
server-principal.tooltip=de Full name of server principal for HTTP service including server and domain name. For example HTTP/host.foo.org@FOO.ORG
|
||||
keytab=de KeyTab
|
||||
keytab.tooltip=de Location of Kerberos KeyTab file containing the credentials of server principal. For example /etc/krb5.keytab
|
||||
debug=de Debug
|
||||
debug.tooltip=de Enable/disable debug logging to standard output for Krb5LoginModule.
|
||||
allow-password-authentication=de Allow Password Authentication
|
||||
allow-password-authentication.tooltip=de Enable/disable possibility of username/password authentication against Kerberos database
|
||||
edit-mode=de Edit Mode
|
||||
edit-mode.tooltip=de READ_ONLY means that password updates are not allowed and user always authenticates with Kerberos password. UNSYNCED means user can change his password in Keycloak database and this one will be used instead of Kerberos password then
|
||||
ldap.edit-mode.tooltip=de READ_ONLY is a read only LDAP store. WRITABLE means data will be synced back to LDAP on demand. UNSYNCED means user data will be imported, but not synced back to LDAP.
|
||||
update-profile-first-login=de Update Profile First Login
|
||||
update-profile-first-login.tooltip=de Update profile on first login
|
||||
sync-registrations=de Sync Registrations
|
||||
ldap.sync-registrations.tooltip=de Should newly created users be created within LDAP store? Priority effects which provider is chose to sync the new user.
|
||||
vendor=de Vendor
|
||||
ldap.vendor.tooltip=de LDAP vendor (provider)
|
||||
username-ldap-attribute=de Username LDAP attribute
|
||||
ldap-attribute-name-for-username=de LDAP attribute name for username
|
||||
username-ldap-attribute.tooltip=de Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server vendors it can be 'uid'. For Active directory it can be 'sAMAccountName' or 'cn'. The attribute should be filled for all LDAP user records you want to import from LDAP to Keycloak.
|
||||
rdn-ldap-attribute=de RDN LDAP attribute
|
||||
ldap-attribute-name-for-user-rdn=de LDAP attribute name for user RDN
|
||||
rdn-ldap-attribute.tooltip=de Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN. Usually it's the same as Username LDAP attribute, however it's not required. For example for Active directory it's common to use 'cn' as RDN attribute when username attribute might be 'sAMAccountName'.
|
||||
uuid-ldap-attribute=de UUID LDAP attribute
|
||||
ldap-attribute-name-for-uuid=de LDAP attribute name for UUID
|
||||
uuid-ldap-attribute.tooltip=de Name of LDAP attribute, which is used as unique object identifier (UUID) for objects in LDAP. For many LDAP server vendors it's 'entryUUID' however some are different. For example for Active directory it should be 'objectGUID'. If your LDAP server really doesn't support the notion of UUID, you can use any other attribute, which is supposed to be unique among LDAP users in tree. For example 'uid' or 'entryDN'.
|
||||
user-object-classes=de User Object Classes
|
||||
ldap-user-object-classes.placeholder=de LDAP User Object Classes (div. by comma)
|
||||
|
||||
ldap-connection-url=de LDAP connection URL
|
||||
ldap-users-dn=de LDAP Users DN
|
||||
ldap-bind-dn=de LDAP Bind DN
|
||||
ldap-bind-credentials=de LDAP Bind Credentials
|
||||
ldap-filter=de LDAP Filter
|
||||
ldap.user-object-classes.tooltip=de All values of LDAP objectClass attribute for users in LDAP divided by comma. For example: 'inetOrgPerson, organizationalPerson' . Newly created Keycloak users will be written to LDAP with all those object classes and existing LDAP user records are found just if they contain all those object classes.\
|
||||
|
||||
connection-url=de Connection URL
|
||||
ldap.connection-url.tooltip=de Connection URL to your LDAP server
|
||||
test-connection=de Test connection
|
||||
users-dn=de Users DN
|
||||
ldap.users-dn.tooltip=de Full DN of LDAP tree where your users are. This DN is parent of LDAP users. It could be for example 'ou=users,dc=example,dc=com' assuming that your typical user will have DN like 'uid=john,ou=users,dc=example,dc=com'
|
||||
authentication-type=de Authentication Type
|
||||
ldap.authentication-type.tooltip=de LDAP Authentication type. Right now just 'none' (anonymous LDAP authentication) or 'simple' (Bind credential + Bind password authentication) mechanisms are available
|
||||
bind-dn=de Bind DN
|
||||
ldap.bind-dn.tooltip=de DN of LDAP admin, which will be used by Keycloak to access LDAP server
|
||||
bind-credential=de Bind Credential
|
||||
ldap.bind-credential.tooltip=de Password of LDAP admin
|
||||
test-authentication=de Test authentication
|
||||
custom-user-ldap-filter=de Custom User LDAP Filter
|
||||
ldap.custom-user-ldap-filter.tooltip=de Additional LDAP Filter for filtering searched users. Leave this empty if you don't need additional filter. Make sure that it starts with '(' and ends with ')'
|
||||
search-scope=de Search Scope
|
||||
ldap.search-scope.tooltip=de For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details
|
||||
connection-pooling=de Connection Pooling
|
||||
ldap.connection-pooling.tooltip=de Does Keycloak should use connection pooling for accessing LDAP server
|
||||
ldap.pagination.tooltip=de Does the LDAP server support pagination.
|
||||
kerberos-integration=de Kerberos Integration
|
||||
allow-kerberos-authentication=de Allow Kerberos authentication
|
||||
ldap.allow-kerberos-authentication.tooltip=de Enable/disable HTTP authentication of users with SPNEGO/Kerberos tokens. The data about authenticated users will be provisioned from this LDAP server
|
||||
use-kerberos-for-password-authentication=de Use Kerberos For Password Authentication
|
||||
ldap.use-kerberos-for-password-authentication.tooltip=de Use Kerberos login module for authenticate username/password against Kerberos server instead of authenticating against LDAP server with Directory Service API
|
||||
batch-size=de Batch Size
|
||||
ldap.batch-size.tooltip=de Count of LDAP users to be imported from LDAP to Keycloak within single transaction.
|
||||
ldap.periodic-full-sync.tooltip=de Does periodic full synchronization of LDAP users to Keycloak should be enabled or not
|
||||
ldap.periodic-changed-users-sync.tooltip=de Does periodic synchronization of changed or newly created LDAP users to Keycloak should be enabled or not
|
||||
ldap.changed-users-sync-period.tooltip=de Period for synchronization of changed or newly created LDAP users in seconds
|
||||
user-federation-mappers=de User Federation Mappers
|
||||
create-user-federation-mapper=de Create user federation mapper
|
||||
add-user-federation-mapper=de Add user federation mapper
|
||||
provider-name=de Provider Name
|
||||
no-user-federation-providers-configured=de No user federation providers configured
|
||||
add-identity-provider=de Add identity provider
|
||||
add-identity-provider-link=de Add identity provider link
|
||||
identity-provider=de Identity Provider
|
||||
identity-provider-user-id=de Identity Provider User ID
|
||||
identity-provider-user-id.tooltip=de Unique ID of the user on the Identity Provider side
|
||||
identity-provider-username=de Identity Provider Username
|
||||
identity-provider-username.tooltip=de Username on the Identity Provider side
|
||||
pagination=de Pagination
|
||||
|
||||
browser-flow=de Browser Flow
|
||||
browser-flow.tooltip=de Select the flow you want to use for browser authentication.
|
||||
registration-flow=de Registration Flow
|
||||
registration-flow.tooltip=de Select the flow you want to use for registration.
|
||||
direct-grant-flow=de Direct Grant Flow
|
||||
direct-grant-flow.tooltip=de Select the flow you want to use for direct grant authentication.
|
||||
reset-credentials=de Reset Credentials
|
||||
reset-credentials.tooltip=de Select the flow you want to use when the user has forgotten their credentials.
|
||||
client-authentication=de Client Authentication
|
||||
client-authentication.tooltip=de Select the flow you want to use for authentication of clients.
|
||||
new=de New
|
||||
copy=de Copy
|
||||
add-execution=de Add execution
|
||||
add-flow=de Add flow
|
||||
auth-type=de Auth Type
|
||||
requirement=de Requirement
|
||||
config=de Config
|
||||
no-executions-available=de No executions available
|
||||
authentication-flows=de Authentication Flows
|
||||
create-authenticator-config=de Create authenticator config
|
||||
authenticator.alias.tooltip=de Name of the configuration
|
||||
otp-type=de OTP Type
|
||||
time-based=de Time Based
|
||||
counter-based=de Counter Based
|
||||
otp-type.tooltip=de totp is Time-Based One Time Password. 'hotp' is a counter base one time password in which the server keeps a counter to hash against.
|
||||
otp-hash-algorithm=de OTP Hash Algorithm
|
||||
otp-hash-algorithm.tooltip=de What hashing algorithm should be used to generate the OTP.
|
||||
number-of-digits=de Number of Digits
|
||||
otp.number-of-digits.tooltip=de How many digits should the OTP have?
|
||||
look-ahead-window=de Look Ahead Window
|
||||
otp.look-ahead-window.tooltip=de How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?
|
||||
initial-counter=de Initial Counter
|
||||
otp.initial-counter.tooltip=de What should the initial counter value be?
|
||||
otp-token-period=de OTP Token Period
|
||||
otp-token-period.tooltip=de How many seconds should an OTP token be valid? Defaults to 30 seconds.
|
||||
table-of-password-policies=de Table of Password Policies
|
||||
add-policy.placeholder=de Add policy...
|
||||
policy-type=de Policy Type
|
||||
policy-value=de Policy Value
|
||||
admin-events=de Admin Events
|
||||
admin-events.tooltip=de Displays saved admin events for the realm. Events are related to admin account, for example a realm creation. To enable persisted events go to config.
|
||||
login-events=de Login Events
|
||||
filter=de Filter
|
||||
update=de Update
|
||||
reset=de Reset
|
||||
operation-types=de Operation Types
|
||||
select-operations.placeholder=de Select operations...
|
||||
resource-path=de Resource Path
|
||||
resource-path.tooltip=de Filter by resource path. Supports wildcards '*' to match a single part of the path and '**' matches multiple parts. For example 'realms/*/clients/asbc' matches client with id asbc in any realm, while or 'realms/master/**' matches anything in the master realm.
|
||||
date-(from)=de Date (From)
|
||||
date-(to)=de Date (To)
|
||||
authentication-details=de Authentication Details
|
||||
ip-address=de IP Address
|
||||
time=de Time
|
||||
operation-type=de Operation Type
|
||||
auth=de Auth
|
||||
representation=de Representation
|
||||
register=de Register
|
||||
required-action=de Required Action
|
||||
default-action=de Default Action
|
||||
auth.default-action.tooltip=de If enabled, any new user will have this required action assigned to it.
|
||||
no-required-actions-configured=de No required actions configured
|
||||
defaults-to-id=de Defaults to id
|
||||
flows=de Flows
|
||||
bindings=de Bindings
|
||||
required-actions=de Required Actions
|
||||
password-policy=de Password Policy
|
||||
otp-policy=de OTP Policy
|
||||
user-groups=de User Groups
|
||||
default-groups=de Default Groups
|
||||
groups.default-groups.tooltip=de Set of groups that new users will automatically join.
|
||||
cut=de Cut
|
||||
paste=de Paste
|
||||
|
||||
create-group=de Create group
|
||||
create-authenticator-execution=de Create Authenticator Execution
|
||||
create-form-action-execution=de Create Form Action Execution
|
||||
create-top-level-form=de Create Top Level Form
|
||||
flow.alias.tooltip=de Specifies display name for the flow.
|
||||
top-level-flow-type=de Top Level Flow Type
|
||||
flow.generic=de generic
|
||||
flow.client=de client
|
||||
top-level-flow-type.tooltip=de What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for users and everything else
|
||||
create-execution-flow=de Create Execution Flow
|
||||
flow-type=de Flow Type
|
||||
flow.form.type=de form
|
||||
flow-type.tooltip=de What kind of form is it
|
||||
form-provider=de Form Provider
|
||||
default-groups.tooltip=de Newly created or registered users will automatically be added to these groups
|
||||
select-a-type.placeholder=de select a type
|
||||
available-groups=de Available Groups
|
||||
available-groups.tooltip=de Select a group you want to add as a default.
|
||||
value=de Value
|
||||
table-of-group-members=de Table of group members
|
||||
last-name=de Last Name
|
||||
first-name=de First Name
|
||||
email=de Email
|
||||
toggle-navigation=de Toggle navigation
|
||||
manage-account=de Manage account
|
||||
sign-out=de Sign Out
|
||||
server-info=de Server Info
|
||||
resource-not-found=de Resource <strong>not found</strong>...
|
||||
resource-not-found.instruction=de We could not find the resource you are looking for. Please make sure the URL you entered is correct.
|
||||
go-to-the-home-page=de Go to the home page »
|
||||
page-not-found=de Page <strong>not found</strong>...
|
||||
page-not-found.instruction=de We could not find the page you are looking for. Please make sure the URL you entered is correct.
|
||||
events.tooltip=de Displays saved events for the realm. Events are related to user accounts, for example a user login. To enable persisted events go to config.
|
||||
select-event-types.placeholder=de Select event types...
|
||||
events-config.tooltip=de Displays configuration options to enable persistence of user and admin events.
|
||||
select-an-action.placeholder=de Select an action...
|
||||
event-listeners.tooltip=de Configure what listeners receive events for the realm.
|
||||
login.save-events.tooltip=de If enabled login events are saved to the database which makes events available to the admin and account management consoles.
|
||||
clear-events.tooltip=de Deletes all events in the database.
|
||||
events.expiration.tooltip=de Sets the expiration for events. Expired events are periodically deleted from the database.
|
||||
admin-events-settings=de Admin Events Settings
|
||||
save-events=de Save events
|
||||
admin.save-events.tooltip=de If enabled admin events are saved to the database which makes events available to the admin console.
|
||||
saved-types.tooltip=de Configure what event types are saved.
|
||||
include-representation=de Include Representation
|
||||
include-representation.tooltip=de Include JSON representation for create and update requests.
|
||||
clear-admin-events.tooltip=de Deletes all admin events in the database.
|
||||
server-version=de Server Version
|
||||
info=de Info
|
||||
providers=de Providers
|
||||
server-time=de Server Time
|
||||
server-uptime=de Server Uptime
|
||||
memory=de Memory
|
||||
total-memory=de Total Memory
|
||||
free-memory=de Free Memory
|
||||
used-memory=de Used Memory
|
||||
system=de System
|
||||
current-working-directory=de Current Working Directory
|
||||
java-version=de Java Version
|
||||
java-vendor=de Java Vendor
|
||||
java-runtime=de Java Runtime
|
||||
java-vm=de Java VM
|
||||
java-vm-version=de Java VM Version
|
||||
java-home=de Java Home
|
||||
user-name=de User Name
|
||||
user-timezone=de User Timezone
|
||||
user-locale=de User Locale
|
||||
system-encoding=de System Encoding
|
||||
operating-system=de Operating System
|
||||
os-architecture=de OS Architecture
|
||||
spi=de SPI
|
||||
granted-roles=de Granted Roles
|
||||
granted-protocol-mappers=de Granted Protocol Mappers
|
||||
additional-grants=de Additional Grants
|
||||
revoke=de Revoke
|
||||
new-password=de New Password
|
||||
password-confirmation=de Password Confirmation
|
||||
credentials.temporary.tooltip=de If enabled user is required to change password on next login
|
||||
remove-totp=de Remove TOTP
|
||||
credentials.remove-totp.tooltip=de Remove one time password generator for user.
|
||||
reset-actions=de Reset Actions
|
||||
credentials.reset-actions.tooltip=de Set of actions to execute when sending the user a Reset Actions Email. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
|
||||
reset-actions-email=de Reset Actions Email
|
||||
send-email=de Send email
|
||||
credentials.reset-actions-email.tooltip=de Sends an email to user with an embedded link. Clicking on link will allow the user to execute the reset actions. They will not have to login prior to this. For example, set the action to update password, click this button, and the user will be able to change their password without logging in.
|
||||
add-user=de Add user
|
||||
created-at=de Created At
|
||||
user-enabled=de User Enabled
|
||||
user-enabled.tooltip=de A disabled user cannot login.
|
||||
user-temporarily-locked=de User Temporarily Locked
|
||||
user-temporarily-locked.tooltip=de The user may have been locked due to failing to login too many times.
|
||||
unlock-user=de Unlock user
|
||||
federation-link=de Federation Link
|
||||
email-verified=de Email Verified
|
||||
email-verified.tooltip=de Has the user's email been verified?
|
||||
required-user-actions=de Required User Actions
|
||||
required-user-actions.tooltip=de Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
|
||||
locale=de Locale
|
||||
select-one.placeholder=de Select one...
|
||||
impersonate=de Impersonate
|
||||
impersonate-user=de Impersonate user
|
||||
impersonate-user.tooltip=de Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.
|
||||
identity-provider-alias=de Identity Provider Alias
|
||||
provider-user-id=de Provider User ID
|
||||
provider-username=de Provider Username
|
||||
no-identity-provider-links-available=de No identity provider links available
|
||||
group-membership=de Group Membership
|
||||
leave=de Leave
|
||||
group-membership.tooltip=de Groups user is a member of. Select a listed group and click the Leave button to leave the group.
|
||||
membership.available-groups.tooltip=de Groups a user can join. Select a group and click the join button.
|
||||
table-of-realm-users=de Table of Realm Users
|
||||
view-all-users=de View all users
|
||||
unlock-users=de Unlock users
|
||||
no-users-available=de No users available
|
||||
users.instruction=de Please enter a search, or click on view all users
|
||||
consents=de Consents
|
||||
started=de Started
|
||||
logout-all-sessions=de Logout all sessions
|
||||
logout=de Logout
|
||||
new-name=de New Name
|
||||
ok=de Ok
|
||||
attributes=de Attributes
|
||||
role-mappings=de Role Mappings
|
||||
members=de Members
|
||||
details=de Details
|
||||
identity-provider-links=de Identity Provider Links
|
||||
register-required-action=de Register required action
|
||||
gender=de Gender
|
||||
address=de Address
|
||||
phone=de Phone
|
||||
profile-url=de Profile URL
|
||||
picture-url=de Picture URL
|
||||
website=de Website
|
||||
import-keys-and-cert=de Import keys and cert
|
||||
import-keys-and-cert.tooltip=de Upload the client's key pair and cert.
|
||||
upload-keys=de Upload Keys
|
||||
download-keys-and-cert=de Download keys and cert
|
||||
no-value-assigned.placeholder=de No value assigned
|
||||
remove=de Remove
|
||||
no-group-members=de No group members
|
||||
temporary=de Temporary
|
||||
join=de Join
|
||||
event-type=de Event Type
|
||||
events-config=de Events Config
|
||||
event-listeners=de Event Listeners
|
||||
login-events-settings=de Login Events Settings
|
||||
clear-events=de Clear Events
|
||||
saved-types=de Saved Types
|
||||
clear-admin-events=de Clear admin events
|
||||
clear-changes=de Clear changes
|
|
@ -908,4 +908,4 @@ clear-events=Clear events
|
|||
saved-types=Saved Types
|
||||
clear-admin-events=Clear admin events
|
||||
clear-changes=Clear changes
|
||||
|
||||
error=Error
|
||||
|
|
|
@ -1730,7 +1730,7 @@ module.controller('ClientTemplateTabCtrl', function(Dialog, $scope, Current, Not
|
|||
|
||||
|
||||
|
||||
module.controller('ClientTemplateListCtrl', function($scope, realm, templates, ClientTemplate, serverInfo, $route, Dialog, Notifications) {
|
||||
module.controller('ClientTemplateListCtrl', function($scope, realm, templates, ClientTemplate, serverInfo, $route, Dialog, Notifications, $location) {
|
||||
$scope.realm = realm;
|
||||
$scope.templates = templates;
|
||||
|
||||
|
|
|
@ -952,19 +952,6 @@ module.factory('ClientOfflineSessions', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ClientLogoutAll', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', {
|
||||
realm : '@realm',
|
||||
client : "@client"
|
||||
});
|
||||
});
|
||||
module.factory('ClientLogoutUser', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-user/:user', {
|
||||
realm : '@realm',
|
||||
client : "@client",
|
||||
user : "@user"
|
||||
});
|
||||
});
|
||||
module.factory('RealmLogoutAll', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/logout-all', {
|
||||
realm : '@realm'
|
||||
|
@ -1315,10 +1302,9 @@ module.factory('PasswordPolicy', function() {
|
|||
if (!policies || policies.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var policyString = "";
|
||||
|
||||
for (var i in policies){
|
||||
for (var i = 0; i < policies.length; i++) {
|
||||
policyString += policies[i].name;
|
||||
if ( policies[i].value ){
|
||||
policyString += '(' + policies[i].value + ')';
|
||||
|
|
|
@ -61,12 +61,8 @@
|
|||
<tr ng-repeat="node in nodeRegistrations">
|
||||
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering/{{node.host}}">{{node.host}}</a></td>
|
||||
<td>{{node.lastRegistration}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/clustering/{{node.host}}">{{:: 'edit' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="removeNode(node)">{{:: 'delete' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/clustering/{{node.host}}">{{:: 'edit' | translate}}</td>
|
||||
<td class="kc-action-cell" data-ng-click="removeNode(node)">{{:: 'delete' | translate}}</td>
|
||||
</tr>
|
||||
<tr data-ng-show="!nodeRegistrations || nodeRegistrations.length == 0">
|
||||
<td class="text-muted">{{:: 'no-registered-cluster-nodes' | translate}}</td>
|
||||
|
|
|
@ -37,9 +37,7 @@
|
|||
<td><span data-ng-show="ia.expiration > 0">{{((ia.timestamp + ia.expiration) * 1000)|date:'shortDate'}} {{((ia.timestamp + ia.expiration) * 1000)|date:'mediumTime'}}</span></td>
|
||||
<td>{{ia.count}}</td>
|
||||
<td>{{ia.remainingCount}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="remove(ia.id)">{{:: 'delete' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell" data-ng-click="remove(ia.id)">{{:: 'delete' | translate}}</td>
|
||||
</tr>
|
||||
<tr data-ng-show="(clients | filter:search).length == 0">
|
||||
<td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
|
||||
|
|
|
@ -40,15 +40,9 @@
|
|||
<a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
|
||||
<span data-ng-hide="client.baseUrl">{{:: 'not-defined' | translate}}</span>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</td>
|
||||
<td class="kc-action-cell" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</td>
|
||||
<td class="kc-action-cell" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</td>
|
||||
</tr>
|
||||
<tr data-ng-show="(clients | filter:search).length == 0">
|
||||
<td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
|
||||
|
|
|
@ -54,12 +54,8 @@
|
|||
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
|
||||
<td>{{mapperTypes[mapper.protocolMapper].category}}</td>
|
||||
<td>{{mapperTypes[mapper.protocolMapper].name}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</button>
|
||||
</td>
|
||||
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</td>
|
||||
<td class="kc-action-cell" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
|
||||
</tr>
|
||||
<tr data-ng-show="mappers.length == 0">
|
||||
<td>{{:: 'no-mappers-available' | translate}}</td>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue