diff --git a/.travis.yml b/.travis.yml index 8146d6e1ed..5dee839fcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,8 @@ language: java jdk: - oraclejdk8 -cache: - directories: - - $HOME/.m2 - -before_cache: - - rm -rf $HOME/.m2/repository/org/keycloak - -install: mvn install -Pdistribution -DskipTests=true -B -V +install: + - travis_wait mvn install -Pdistribution -DskipTests=true -B -V -q script: - mvn test -B diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java index 6da630f557..a67741968e 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java @@ -92,4 +92,9 @@ public abstract class AbstractIdentityProvider public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) { } + + @Override + public IdentityProviderDataMarshaller getMarshaller() { + return new DefaultDataMarshaller(); + } } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java new file mode 100644 index 0000000000..3f8fcf2f48 --- /dev/null +++ b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java @@ -0,0 +1,40 @@ +package org.keycloak.broker.provider; + +import java.io.IOException; + +import org.keycloak.common.util.Base64Url; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class DefaultDataMarshaller implements IdentityProviderDataMarshaller { + + @Override + public String serialize(Object value) { + if (value instanceof String) { + return (String) value; + } else { + try { + byte[] bytes = JsonSerialization.writeValueAsBytes(value); + return Base64Url.encode(bytes); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + } + + @Override + public T deserialize(String serialized, Class clazz) { + if (clazz.equals(String.class)) { + return clazz.cast(serialized); + } else { + byte[] bytes = Base64Url.decode(serialized); + try { + return JsonSerialization.readValue(bytes, clazz); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + } +} diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java index 1d775eec5c..42eb6fedf4 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java @@ -103,4 +103,10 @@ public interface IdentityProvider extends Provi */ Response export(UriInfo uriInfo, RealmModel realm, String format); + /** + * Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession + * @return + */ + IdentityProviderDataMarshaller getMarshaller(); + } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java new file mode 100644 index 0000000000..7e57653613 --- /dev/null +++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderDataMarshaller.java @@ -0,0 +1,12 @@ +package org.keycloak.broker.provider; + +/** + * + * @author Marek Posolda + */ +public interface IdentityProviderDataMarshaller { + + String serialize(Object obj); + T deserialize(String serialized, Class clazz); + +} diff --git a/broker/saml/pom.xml b/broker/saml/pom.xml index 858a7ba21a..21a00994f0 100755 --- a/broker/saml/pom.xml +++ b/broker/saml/pom.xml @@ -45,6 +45,11 @@ jboss-logging provided + + junit + junit + test + diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java new file mode 100644 index 0000000000..61f4d8af7a --- /dev/null +++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLDataMarshaller.java @@ -0,0 +1,88 @@ +package org.keycloak.broker.saml; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import javax.xml.stream.XMLEventReader; + +import org.keycloak.broker.provider.DefaultDataMarshaller; +import org.keycloak.dom.saml.v2.assertion.AssertionType; +import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; +import org.keycloak.dom.saml.v2.protocol.ResponseType; +import org.keycloak.saml.common.exceptions.ParsingException; +import org.keycloak.saml.common.exceptions.ProcessingException; +import org.keycloak.saml.common.util.StaxUtil; +import org.keycloak.saml.processing.core.parsers.saml.SAMLParser; +import org.keycloak.saml.processing.core.parsers.util.SAMLParserUtil; +import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter; +import org.keycloak.saml.processing.core.saml.v2.writers.SAMLResponseWriter; + +/** + * @author Marek Posolda + */ +public class SAMLDataMarshaller extends DefaultDataMarshaller { + + @Override + public String serialize(Object obj) { + + // Lame impl, but hopefully sufficient for now. See if something better is needed... + if (obj.getClass().getName().startsWith("org.keycloak.dom.saml")) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + try { + if (obj instanceof ResponseType) { + ResponseType responseType = (ResponseType) obj; + SAMLResponseWriter samlWriter = new SAMLResponseWriter(StaxUtil.getXMLStreamWriter(bos)); + samlWriter.write(responseType); + } else if (obj instanceof AssertionType) { + AssertionType assertion = (AssertionType) obj; + SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos)); + samlWriter.write(assertion); + } else if (obj instanceof AuthnStatementType) { + AuthnStatementType authnStatement = (AuthnStatementType) obj; + SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos)); + samlWriter.write(authnStatement, true); + } else { + throw new IllegalArgumentException("Don't know how to serialize object of type " + obj.getClass().getName()); + } + } catch (ProcessingException pe) { + throw new RuntimeException(pe); + } + + return new String(bos.toByteArray()); + } else { + return super.serialize(obj); + } + } + + @Override + public T deserialize(String serialized, Class clazz) { + if (clazz.getName().startsWith("org.keycloak.dom.saml")) { + String xmlString = serialized; + + try { + if (clazz.equals(ResponseType.class) || clazz.equals(AssertionType.class)) { + byte[] bytes = xmlString.getBytes(); + InputStream is = new ByteArrayInputStream(bytes); + Object respType = new SAMLParser().parse(is); + return clazz.cast(respType); + } else if (clazz.equals(AuthnStatementType.class)) { + byte[] bytes = xmlString.getBytes(); + InputStream is = new ByteArrayInputStream(bytes); + XMLEventReader xmlEventReader = new SAMLParser().createEventReader(is); + AuthnStatementType authnStatement = SAMLParserUtil.parseAuthnStatement(xmlEventReader); + return clazz.cast(authnStatement); + } else { + throw new IllegalArgumentException("Don't know how to deserialize object of type " + clazz.getName()); + } + } catch (ParsingException pe) { + throw new RuntimeException(pe); + } + + } else { + return super.deserialize(serialized, clazz); + } + } + +} diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java index 248c4de35b..0014470637 100755 --- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java @@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider; import org.keycloak.broker.provider.AuthenticationRequest; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; +import org.keycloak.broker.provider.IdentityProviderDataMarshaller; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; @@ -263,4 +264,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProviderMarek Posolda + */ +public class SAMLDataMarshallerTest { + + private static final String TEST_RESPONSE = "http://localhost:8082/auth/realms/realm-with-saml-idp-basichttp://localhost:8082/auth/realms/realm-with-saml-idp-basictest-userhttp://localhost:8081/auth/realms/realm-with-brokerurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified617-666-7777test-user@localhostmanager"; + + private static final String TEST_ASSERTION = "http://localhost:8082/auth/realms/realm-with-saml-idp-basictest-userhttp://localhost:8081/auth/realms/realm-with-brokerurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified617-666-7777test-user@localhostmanager"; + + private static final String TEST_AUTHN_TYPE = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"; + + @Test + public void testParseResponse() throws Exception { + SAMLDataMarshaller serializer = new SAMLDataMarshaller(); + ResponseType responseType = serializer.deserialize(TEST_RESPONSE, ResponseType.class); + + // test ResponseType + Assert.assertEquals(responseType.getID(), "ID_4804cf50-cd96-4b92-823e-89adaa0c78ba"); + Assert.assertEquals(responseType.getDestination(), "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint"); + Assert.assertEquals(responseType.getIssuer().getValue(), "http://localhost:8082/auth/realms/realm-with-saml-idp-basic"); + Assert.assertEquals(responseType.getAssertions().get(0).getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9"); + + // back to String + String serialized = serializer.serialize(responseType); + Assert.assertEquals(TEST_RESPONSE, serialized); + } + + @Test + public void testParseAssertion() throws Exception { + SAMLDataMarshaller serializer = new SAMLDataMarshaller(); + AssertionType assertion = serializer.deserialize(TEST_ASSERTION, AssertionType.class); + + // test assertion + Assert.assertEquals(assertion.getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9"); + Assert.assertEquals(((NameIDType) assertion.getSubject().getSubType().getBaseID()).getValue(), "test-user"); + + // back to String + String serialized = serializer.serialize(assertion); + Assert.assertEquals(TEST_ASSERTION, serialized); + } + + @Test + public void testParseAuthnType() throws Exception { + SAMLDataMarshaller serializer = new SAMLDataMarshaller(); + AuthnStatementType authnStatement = serializer.deserialize(TEST_AUTHN_TYPE, AuthnStatementType.class); + + // test authnStatement + Assert.assertEquals(authnStatement.getSessionIndex(), "fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5"); + + // back to String + String serialized = serializer.serialize(authnStatement); + Assert.assertEquals(TEST_AUTHN_TYPE, serialized); + } +} diff --git a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java deleted file mode 100644 index 82a3b3759b..0000000000 --- a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java +++ /dev/null @@ -1,275 +0,0 @@ -package org.keycloak.client.registration; - -import org.apache.http.HttpHeaders; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.common.util.Base64; -import org.keycloak.util.JsonSerialization; - -import java.io.IOException; -import java.io.InputStream; - -/** - * @author Stian Thorgersen - */ -public class ClientRegistration { - - private String clientRegistrationUrl; - private HttpClient httpClient; - private Auth auth; - - public static ClientRegistrationBuilder create() { - return new ClientRegistrationBuilder(); - } - - private ClientRegistration() { - } - - public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException { - String content = serialize(client); - InputStream resultStream = doPost(content); - return deserialize(resultStream, ClientRepresentation.class); - } - - public ClientRepresentation get() throws ClientRegistrationException { - if (auth instanceof ClientIdSecretAuth) { - String clientId = ((ClientIdSecretAuth) auth).clientId; - return get(clientId); - } else { - throw new ClientRegistrationException("Requires client authentication"); - } - } - - public ClientRepresentation get(String clientId) throws ClientRegistrationException { - InputStream resultStream = doGet(clientId); - return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; - } - - public void update(ClientRepresentation client) throws ClientRegistrationException { - String content = serialize(client); - doPut(content, client.getClientId()); - } - - public void delete() throws ClientRegistrationException { - if (auth instanceof ClientIdSecretAuth) { - String clientId = ((ClientIdSecretAuth) auth).clientId; - delete(clientId); - } else { - throw new ClientRegistrationException("Requires client authentication"); - } - } - - public void delete(String clientId) throws ClientRegistrationException { - doDelete(clientId); - } - - public void close() throws ClientRegistrationException { - if (httpClient instanceof CloseableHttpClient) { - try { - ((CloseableHttpClient) httpClient).close(); - } catch (IOException e) { - throw new ClientRegistrationException("Failed to close http client", e); - } - } - } - - private InputStream doPost(String content) throws ClientRegistrationException { - try { - HttpPost request = new HttpPost(clientRegistrationUrl); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setHeader(HttpHeaders.ACCEPT, "application/json"); - request.setEntity(new StringEntity(content)); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - InputStream responseStream = null; - if (response.getEntity() != null) { - responseStream = response.getEntity().getContent(); - } - - if (response.getStatusLine().getStatusCode() == 201) { - return responseStream; - } else { - responseStream.close(); - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private InputStream doGet(String endpoint) throws ClientRegistrationException { - try { - HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint); - - request.setHeader(HttpHeaders.ACCEPT, "application/json"); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - InputStream responseStream = null; - if (response.getEntity() != null) { - responseStream = response.getEntity().getContent(); - } - - if (response.getStatusLine().getStatusCode() == 200) { - return responseStream; - } else if (response.getStatusLine().getStatusCode() == 404) { - responseStream.close(); - return null; - } else { - responseStream.close(); - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private void doPut(String content, String endpoint) throws ClientRegistrationException { - try { - HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setHeader(HttpHeaders.ACCEPT, "application/json"); - request.setEntity(new StringEntity(content)); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - if (response.getEntity() != null) { - response.getEntity().getContent().close(); - } - - if (response.getStatusLine().getStatusCode() != 200) { - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private void doDelete(String endpoint) throws ClientRegistrationException { - try { - HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - if (response.getEntity() != null) { - response.getEntity().getContent().close(); - } - - if (response.getStatusLine().getStatusCode() != 200) { - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private String serialize(ClientRepresentation client) throws ClientRegistrationException { - try { - return JsonSerialization.writeValueAsString(client); - } catch (IOException e) { - throw new ClientRegistrationException("Failed to write json object", e); - } - } - - private T deserialize(InputStream inputStream, Class clazz) throws ClientRegistrationException { - try { - return JsonSerialization.readValue(inputStream, clazz); - } catch (IOException e) { - throw new ClientRegistrationException("Failed to read json object", e); - } - } - - public static class ClientRegistrationBuilder { - - private String realm; - - private String authServerUrl; - - private Auth auth; - - private HttpClient httpClient; - - public ClientRegistrationBuilder realm(String realm) { - this.realm = realm; - return this; - } - public ClientRegistrationBuilder authServerUrl(String authServerUrl) { - this.authServerUrl = authServerUrl; - return this; - } - - public ClientRegistrationBuilder auth(String token) { - this.auth = new TokenAuth(token); - return this; - } - - public ClientRegistrationBuilder auth(String clientId, String clientSecret) { - this.auth = new ClientIdSecretAuth(clientId, clientSecret); - return this; - } - - public ClientRegistrationBuilder httpClient(HttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - public ClientRegistration build() { - ClientRegistration clientRegistration = new ClientRegistration(); - clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default"; - - clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault(); - clientRegistration.auth = auth; - - return clientRegistration; - } - - } - - public interface Auth { - void addAuth(HttpRequest httpRequest); - } - - public static class AuthorizationHeaderAuth implements Auth { - private String credentials; - - public AuthorizationHeaderAuth(String credentials) { - this.credentials = credentials; - } - - public void addAuth(HttpRequest httpRequest) { - httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials); - } - } - - public static class TokenAuth extends AuthorizationHeaderAuth { - public TokenAuth(String token) { - super("Bearer " + token); - } - } - - public static class ClientIdSecretAuth extends AuthorizationHeaderAuth { - private String clientId; - - public ClientIdSecretAuth(String clientId, String clientSecret) { - super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes())); - this.clientId = clientId; - } - } - -} diff --git a/client-api/pom.xml b/client-registration/api/pom.xml similarity index 71% rename from client-api/pom.xml rename to client-registration/api/pom.xml index e1c1b5c71a..30c911b14d 100755 --- a/client-api/pom.xml +++ b/client-registration/api/pom.xml @@ -2,14 +2,14 @@ - keycloak-parent + keycloak-client-registration-parent org.keycloak 1.7.0.Final-SNAPSHOT 4.0.0 - keycloak-client-api - Keycloak Client API + keycloak-client-registration-api + Keycloak Client Registration API @@ -21,11 +21,6 @@ org.apache.httpcomponents httpclient - - junit - junit - test - diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java new file mode 100644 index 0000000000..c3dd313921 --- /dev/null +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/Auth.java @@ -0,0 +1,68 @@ +package org.keycloak.client.registration; + +import org.apache.http.HttpHeaders; +import org.apache.http.HttpRequest; +import org.keycloak.common.util.Base64; +import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; + +/** + * @author Stian Thorgersen + */ +public abstract class Auth { + + public abstract void addAuth(HttpRequest request); + + public static Auth token(String token) { + return new BearerTokenAuth(token); + } + + public static Auth token(ClientInitialAccessPresentation initialAccess) { + return new BearerTokenAuth(initialAccess.getToken()); + } + + public static Auth token(ClientRepresentation client) { + return new BearerTokenAuth(client.getRegistrationAccessToken()); + } + + public static Auth token(OIDCClientRepresentation client) { + return new BearerTokenAuth(client.getRegistrationAccessToken()); + } + + public static Auth client(String clientId, String clientSecret) { + return new BasicAuth(clientId, clientSecret); + } + + private static class BearerTokenAuth extends Auth { + + private String token; + + public BearerTokenAuth(String token) { + this.token = token; + } + + @Override + public void addAuth(HttpRequest request) { + request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); + } + } + + private static class BasicAuth extends Auth { + + private String username; + private String password; + + public BasicAuth(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public void addAuth(HttpRequest request) { + String val = Base64.encodeBytes((username + ":" + password).getBytes()); + request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + val); + } + } + +} diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java new file mode 100644 index 0000000000..f2215f19b9 --- /dev/null +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistration.java @@ -0,0 +1,186 @@ +package org.keycloak.client.registration; + +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClients; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.keycloak.representations.adapters.config.AdapterConfig; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Stian Thorgersen + */ +public class ClientRegistration { + + public static final ObjectMapper outputMapper = new ObjectMapper(); + static { + outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class); + outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class); + outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL); + } + + private final String JSON = "application/json"; + private final String XML = "application/xml"; + + private final String DEFAULT = "default"; + private final String INSTALLATION = "install"; + private final String OIDC = "openid-connect"; + private final String SAML = "saml2-entity-descriptor"; + + private HttpUtil httpUtil; + + public static ClientRegistrationBuilder create() { + return new ClientRegistrationBuilder(); + } + + ClientRegistration(HttpUtil httpUtil) { + this.httpUtil = httpUtil; + } + + public void close() throws ClientRegistrationException { + if (httpUtil != null) { + httpUtil.close(); + } + httpUtil = null; + } + + public ClientRegistration auth(Auth auth) { + httpUtil.setAuth(auth); + return this; + } + + public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException { + String content = serialize(client); + InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT); + return deserialize(resultStream, ClientRepresentation.class); + } + + public ClientRepresentation get(String clientId) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId); + return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; + } + + public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId); + return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null; + } + + public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException { + String content = serialize(client); + InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId()); + return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; + } + + public void delete(ClientRepresentation client) throws ClientRegistrationException { + delete(client.getClientId()); + } + + public void delete(String clientId) throws ClientRegistrationException { + httpUtil.doDelete(DEFAULT, clientId); + } + + public OIDCClientRegistration oidc() { + return new OIDCClientRegistration(); + } + + public SAMLClientRegistration saml() { + return new SAMLClientRegistration(); + } + + public static String serialize(Object obj) throws ClientRegistrationException { + try { + return outputMapper.writeValueAsString(obj); + } catch (IOException e) { + throw new ClientRegistrationException("Failed to write json object", e); + } + } + + private static T deserialize(InputStream inputStream, Class clazz) throws ClientRegistrationException { + try { + return JsonSerialization.readValue(inputStream, clazz); + } catch (IOException e) { + throw new ClientRegistrationException("Failed to read json object", e); + } + } + + public class OIDCClientRegistration { + + public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException { + String content = serialize(client); + InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC); + return deserialize(resultStream, OIDCClientRepresentation.class); + } + + public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId); + return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null; + } + + public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException { + String content = serialize(client); + InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId()); + return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null; + } + + public void delete(OIDCClientRepresentation client) throws ClientRegistrationException { + delete(client.getClientId()); + } + + public void delete(String clientId) throws ClientRegistrationException { + httpUtil.doDelete(OIDC, clientId); + } + + } + + public class SAMLClientRegistration { + + public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML); + return deserialize(resultStream, ClientRepresentation.class); + } + + } + + public static class ClientRegistrationBuilder { + + private String url; + private HttpClient httpClient; + + ClientRegistrationBuilder() { + } + + public ClientRegistrationBuilder url(String realmUrl) { + url = realmUrl; + return this; + } + + public ClientRegistrationBuilder url(String authUrl, String realm) { + url = HttpUtil.getUrl(authUrl, "realms", realm, "clients"); + return this; + } + + public ClientRegistrationBuilder httpClient(HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ClientRegistration build() { + if (url == null) { + throw new IllegalStateException("url not configured"); + } + + if (httpClient == null) { + httpClient = HttpClients.createDefault(); + } + + return new ClientRegistration(new HttpUtil(httpClient, url)); + } + + } + +} diff --git a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistrationException.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistrationException.java similarity index 100% rename from client-api/src/main/java/org/keycloak/client/registration/ClientRegistrationException.java rename to client-registration/api/src/main/java/org/keycloak/client/registration/ClientRegistrationException.java diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java new file mode 100644 index 0000000000..ba382f6717 --- /dev/null +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/ClientRepresentationMixIn.java @@ -0,0 +1,13 @@ +package org.keycloak.client.registration; + +import org.codehaus.jackson.annotate.JsonIgnore; + +/** + * @author Stian Thorgersen + */ +abstract class ClientRepresentationMixIn { + + @JsonIgnore + String registrationAccessToken; + +} diff --git a/client-api/src/main/java/org/keycloak/client/registration/HttpErrorException.java b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpErrorException.java similarity index 100% rename from client-api/src/main/java/org/keycloak/client/registration/HttpErrorException.java rename to client-registration/api/src/main/java/org/keycloak/client/registration/HttpErrorException.java diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java new file mode 100644 index 0000000000..4d44710f5b --- /dev/null +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/HttpUtil.java @@ -0,0 +1,171 @@ +package org.keycloak.client.registration; + +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.HttpErrorException; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Stian Thorgersen + */ +class HttpUtil { + + private HttpClient httpClient; + + private String baseUri; + + private Auth auth; + + HttpUtil(HttpClient httpClient, String baseUri) { + this.httpClient = httpClient; + this.baseUri = baseUri; + } + + void setAuth(Auth auth) { + this.auth = auth; + } + + InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException { + try { + HttpPost request = new HttpPost(getUrl(baseUri, path)); + + request.setHeader(HttpHeaders.CONTENT_TYPE, contentType); + request.setHeader(HttpHeaders.ACCEPT, acceptType); + request.setEntity(new StringEntity(content)); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + InputStream responseStream = null; + if (response.getEntity() != null) { + responseStream = response.getEntity().getContent(); + } + + if (response.getStatusLine().getStatusCode() == 201) { + return responseStream; + } else { + responseStream.close(); + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + InputStream doGet(String acceptType, String... path) throws ClientRegistrationException { + try { + HttpGet request = new HttpGet(getUrl(baseUri, path)); + + request.setHeader(HttpHeaders.ACCEPT, acceptType); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + InputStream responseStream = null; + if (response.getEntity() != null) { + responseStream = response.getEntity().getContent(); + } + + if (response.getStatusLine().getStatusCode() == 200) { + return responseStream; + } else if (response.getStatusLine().getStatusCode() == 404) { + responseStream.close(); + return null; + } else { + if (responseStream != null) { + responseStream.close(); + } + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException { + try { + HttpPut request = new HttpPut(getUrl(baseUri, path)); + + request.setHeader(HttpHeaders.CONTENT_TYPE, contentType); + request.setHeader(HttpHeaders.ACCEPT, acceptType); + request.setEntity(new StringEntity(content)); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + if (response.getEntity() != null) { + response.getEntity().getContent(); + } + + InputStream responseStream = null; + if (response.getEntity() != null) { + responseStream = response.getEntity().getContent(); + } + + if (response.getStatusLine().getStatusCode() == 200) { + return responseStream; + } else { + if (responseStream != null) { + responseStream.close(); + } + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + void doDelete(String... path) throws ClientRegistrationException { + try { + HttpDelete request = new HttpDelete(getUrl(baseUri, path)); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + if (response.getEntity() != null) { + response.getEntity().getContent().close(); + } + + if (response.getStatusLine().getStatusCode() != 204) { + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + void close() throws ClientRegistrationException { + if (httpClient instanceof CloseableHttpClient) { + try { + ((CloseableHttpClient) httpClient).close(); + } catch (IOException e) { + throw new ClientRegistrationException("Failed to close http client", e); + } + } + } + + static String getUrl(String baseUri, String... path) { + StringBuilder s = new StringBuilder(); + s.append(baseUri); + for (String p : path) { + s.append('/'); + s.append(p); + } + return s.toString(); + } + + private void addAuth(HttpRequestBase request) { + if (auth != null) { + auth.addAuth(request); + } + } + +} diff --git a/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java new file mode 100644 index 0000000000..b0dfed637e --- /dev/null +++ b/client-registration/api/src/main/java/org/keycloak/client/registration/OIDCClientRepresentationMixIn.java @@ -0,0 +1,22 @@ +package org.keycloak.client.registration; + +import org.codehaus.jackson.annotate.JsonIgnore; + +/** + * @author Stian Thorgersen + */ +abstract class OIDCClientRepresentationMixIn { + + @JsonIgnore + private Integer client_id_issued_at; + + @JsonIgnore + private Integer client_secret_expires_at; + + @JsonIgnore + private String registration_client_uri; + + @JsonIgnore + private String registration_access_token; + +} diff --git a/client-registration/cli/pom.xml b/client-registration/cli/pom.xml new file mode 100755 index 0000000000..13b8a8f91e --- /dev/null +++ b/client-registration/cli/pom.xml @@ -0,0 +1,34 @@ + + + + keycloak-client-registration-parent + org.keycloak + 1.7.0.Final-SNAPSHOT + + 4.0.0 + + keycloak-client-registration-cli + Keycloak Client Registration CLI + + + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-client-registration-api + + + org.apache.httpcomponents + httpclient + + + org.jboss.aesh + aesh + + + + diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java new file mode 100644 index 0000000000..986faac648 --- /dev/null +++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/ClientRegistrationCLI.java @@ -0,0 +1,70 @@ +package org.keycloak.client.registration.cli; + +import org.jboss.aesh.cl.parser.CommandLineParserException; +import org.jboss.aesh.console.AeshConsole; +import org.jboss.aesh.console.AeshConsoleBuilder; +import org.jboss.aesh.console.Prompt; +import org.jboss.aesh.console.command.Command; +import org.jboss.aesh.console.command.CommandNotFoundException; +import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder; +import org.jboss.aesh.console.settings.Settings; +import org.jboss.aesh.console.settings.SettingsBuilder; +import org.jboss.aesh.terminal.Color; +import org.jboss.aesh.terminal.TerminalColor; +import org.jboss.aesh.terminal.TerminalString; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.client.registration.cli.commands.CreateCommand; +import org.keycloak.client.registration.cli.commands.ExitCommand; +import org.keycloak.client.registration.cli.commands.SetupCommand; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +public class ClientRegistrationCLI { + + private static ClientRegistration reg; + + public static void main(String[] args) throws CommandLineParserException, CommandNotFoundException { + reg = ClientRegistration.create().url("http://localhost:8080/auth/realms/master").build(); + reg.auth(Auth.token("...")); + + Context context = new Context(); + + List commands = new LinkedList<>(); + commands.add(new SetupCommand(context)); + commands.add(new CreateCommand(context)); + commands.add(new ExitCommand(context)); + + SettingsBuilder builder = new SettingsBuilder().logging(true); + builder.enableMan(true).readInputrc(false); + + Settings settings = builder.create(); + + AeshCommandRegistryBuilder commandRegistryBuilder = new AeshCommandRegistryBuilder(); + for (Command c : commands) { + commandRegistryBuilder.command(c); + } + + AeshConsole aeshConsole = new AeshConsoleBuilder() + .commandRegistry(commandRegistryBuilder.create()) + .settings(settings) + .prompt(new Prompt(new TerminalString("[clientreg]$ ", + new TerminalColor(Color.GREEN, Color.DEFAULT, Color.Intensity.BRIGHT)))) + .create(); + + aeshConsole.start(); +/* + if (args.length > 0) { + CommandContainer command = registry.getCommand(args[0], null); + ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length)); + }*/ + + //commandInvocation.getCommandRegistry().getAllCommandNames() + } + +} + diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/Context.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/Context.java new file mode 100644 index 0000000000..49d8fb90ac --- /dev/null +++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/Context.java @@ -0,0 +1,37 @@ +package org.keycloak.client.registration.cli; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.util.SystemPropertiesJsonParserFactory; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Stian Thorgersen + */ +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); + } + + private ClientRegistration reg; + + public ClientRegistration getReg() { + return reg; + } + + public void setReg(ClientRegistration reg) { + this.reg = reg; + } + + public static T readJson(InputStream bytes, Class type) throws IOException { + return mapper.readValue(bytes, type); + } + +} diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/CreateCommand.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/CreateCommand.java new file mode 100644 index 0000000000..280534b1db --- /dev/null +++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/CreateCommand.java @@ -0,0 +1,64 @@ +package org.keycloak.client.registration.cli.commands; + +import org.jboss.aesh.cl.Arguments; +import org.jboss.aesh.cl.CommandDefinition; +import org.jboss.aesh.cl.Option; +import org.jboss.aesh.console.command.Command; +import org.jboss.aesh.console.command.CommandResult; +import org.jboss.aesh.console.command.invocation.CommandInvocation; +import org.jboss.aesh.io.Resource; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.cli.Context; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +@CommandDefinition(name="create", description = "[OPTIONS] FILE") +public class CreateCommand implements Command { + + @Option(shortName = 'h', hasValue = false, description = "display this help and exit") + private boolean help; + + @Arguments(description = "files or directories thats listed") + private List arguments; + + private Context context; + + public CreateCommand(Context context) { + this.context = context; + } + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException { + System.out.println(help); + + + if(help) { + commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create")); + } + else { + + if(arguments != null) { + for(Resource f : arguments) { + System.out.println(f.getAbsolutePath()); + ClientRepresentation rep = JsonSerialization.readValue(f.read(), ClientRepresentation.class); + try { + context.getReg().create(rep); + } catch (ClientRegistrationException e) { + e.printStackTrace(); + } + } + + } + } +// reg.create(); + + return CommandResult.SUCCESS; + } + +} diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/ExitCommand.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/ExitCommand.java new file mode 100644 index 0000000000..507881bd25 --- /dev/null +++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/ExitCommand.java @@ -0,0 +1,29 @@ +package org.keycloak.client.registration.cli.commands; + +import org.jboss.aesh.cl.CommandDefinition; +import org.jboss.aesh.console.command.Command; +import org.jboss.aesh.console.command.CommandResult; +import org.jboss.aesh.console.command.invocation.CommandInvocation; +import org.keycloak.client.registration.cli.Context; + +import java.io.IOException; + +/** + * @author Stian Thorgersen + */ +@CommandDefinition(name="exit", description = "Exit the program") +public class ExitCommand implements Command { + + private Context context; + + public ExitCommand(Context context) { + this.context = context; + } + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException { + commandInvocation.stop(); + return CommandResult.SUCCESS; + } + +} diff --git a/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/SetupCommand.java b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/SetupCommand.java new file mode 100644 index 0000000000..26579abd4e --- /dev/null +++ b/client-registration/cli/src/main/java/org/keycloak/client/registration/cli/commands/SetupCommand.java @@ -0,0 +1,48 @@ +package org.keycloak.client.registration.cli.commands; + +import org.jboss.aesh.cl.CommandDefinition; +import org.jboss.aesh.cl.Option; +import org.jboss.aesh.console.command.Command; +import org.jboss.aesh.console.command.CommandResult; +import org.jboss.aesh.console.command.invocation.CommandInvocation; +import org.jboss.aesh.io.Resource; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.cli.Context; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; + +/** + * @author Stian Thorgersen + */ +@CommandDefinition(name="setup", description = "") +public class SetupCommand implements Command { + + @Option(shortName = 'h', hasValue = false, description = "display this help and exit") + private boolean help; + + private Context context; + + public SetupCommand(Context context) { + this.context = context; + } + + @Override + public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException { + System.out.println(help); + + if(help) { + commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create")); + } + + return CommandResult.SUCCESS; + } + + + private String promptForUsername(CommandInvocation invocation) throws InterruptedException { + invocation.print("username: "); + return invocation.getInputLine(); + } + +} diff --git a/client-registration/pom.xml b/client-registration/pom.xml new file mode 100755 index 0000000000..9d1ea9f5a9 --- /dev/null +++ b/client-registration/pom.xml @@ -0,0 +1,19 @@ + + + keycloak-parent + org.keycloak + 1.7.0.Final-SNAPSHOT + + + Keycloak Client Registration Parent + + 4.0.0 + keycloak-client-registration-parent + pom + + + api + + + diff --git a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java index cec9ea984b..bec8acf648 100644 --- a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java +++ b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java @@ -13,7 +13,7 @@ public class ObjectUtil { * @param str2 * @return true if both strings are null or equal */ - public static boolean isEqualOrNull(Object str1, Object str2) { + public static boolean isEqualOrBothNull(Object str1, Object str2) { if (str1 == null && str2 == null) { return true; } @@ -24,4 +24,8 @@ public class ObjectUtil { return str1.equals(str2); } + + public static String capitalize(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1); + } } diff --git a/connections/file/pom.xml b/connections/file/pom.xml deleted file mode 100755 index a4a749021f..0000000000 --- a/connections/file/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - keycloak-parent - org.keycloak - 1.7.0.Final-SNAPSHOT - ../../pom.xml - - 4.0.0 - - keycloak-connections-file - Keycloak Connections File - - - - - org.keycloak - keycloak-export-import-api - - - org.keycloak - keycloak-export-import-single-file - - - org.keycloak - keycloak-core - - - org.keycloak - keycloak-model-api - - - org.codehaus.jackson - jackson-mapper-asl - provided - - - org.jboss.logging - jboss-logging - - - diff --git a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java b/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java deleted file mode 100644 index b821943901..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.keycloak.connections.file; - -import org.keycloak.models.KeycloakSession; - -/** - * Provides the InMemoryModel and notifies the factory to save it when - * the session is done. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class DefaultFileConnectionProvider implements FileConnectionProvider { - - private final DefaultFileConnectionProviderFactory factory; - private final KeycloakSession session; - private final InMemoryModel inMemoryModel; - - private boolean isRollbackOnly = false; - - public DefaultFileConnectionProvider(DefaultFileConnectionProviderFactory factory, - KeycloakSession session, - InMemoryModel inMemoryModel) { - this.factory = factory; - this.session = session; - this.inMemoryModel = inMemoryModel; - } - - @Override - public InMemoryModel getModel() { - return inMemoryModel; - } - - @Override - public void sessionClosed(KeycloakSession session) { - factory.sessionClosed(session); - } - - @Override - public void close() { - } - - @Override - public void begin() { - } - - @Override - public void commit() { - factory.commit(session); - } - - @Override - public void rollback() { - factory.rollback(session); - } - - @Override - public void setRollbackOnly() { - isRollbackOnly = true; - } - - @Override - public boolean getRollbackOnly() { - return isRollbackOnly; - } - - @Override - public boolean isActive() { - return factory.isActive(session); - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java b/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java deleted file mode 100755 index 9bfe36248c..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.keycloak.connections.file; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.codehaus.jackson.JsonToken; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.exportimport.Strategy; -import org.keycloak.exportimport.util.ExportUtils; -import org.keycloak.exportimport.util.ImportUtils; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.util.JsonSerialization; - -/** - * This class dispenses a FileConnectionProvider to Keycloak sessions. It - * makes sure that only one InMemoryModel is provided for each session and it - * handles thread contention for the file where the model is read or saved. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class DefaultFileConnectionProviderFactory implements FileConnectionProviderFactory { - - protected static final Logger logger = Logger.getLogger(DefaultFileConnectionProviderFactory.class); - - private File kcdata; - private final Map allProviders = new HashMap(); - - @Override - public void init(Config.Scope config) { - String fileName = config.get("fileName"); - if (fileName == null) { - fileName = "keycloak-model.json"; - } - - String directory = config.get("directory"); - if (directory == null) { - directory = System.getProperty("jboss.server.data.dir"); - } - if (directory == null) { - directory = "."; - } - - kcdata = new File(directory, fileName); - } - - public void sessionClosed(KeycloakSession session) { - synchronized(allProviders) { - allProviders.remove(session); - //logger.info("Removed session " + session.hashCode()); - //logger.info("sessionClosed: Session count=" + allModels.size()); - } - } - - void readModelFile(KeycloakSession session) { - synchronized(allProviders) { - if (!kcdata.exists()) { - return; - } - - FileInputStream fis = null; - try { - fis = new FileInputStream(kcdata); - Model model = JsonSerialization.readValue(fis, Model.class); - ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING); - session.realms().getMigrationModel().setStoredVersion(model.getModelVersion()); - - ImportUtils.importRealms(session, model.getRealms(), Strategy.IGNORE_EXISTING); - } catch (IOException ioe) { - logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe); - } finally { - //logger.info("Read model file for session=" + session.hashCode()); - try { - if (fis != null) { - fis.close(); - } - } catch (IOException e) { - logger.error("Failed to close output stream.", e); - } - } - } - } - - void writeModelFile(KeycloakSession session) { - synchronized(allProviders) { - FileOutputStream outStream = null; - - try { - outStream = new FileOutputStream(kcdata); - exportModel(session, outStream); - } catch (IOException e) { - logger.error("Unable to write model file " + kcdata.getAbsolutePath(), e); - } finally { - //logger.info("Wrote model file for session=" + session.hashCode()); - try { - if (outStream != null) { - outStream.close(); - } - } catch (IOException e) { - logger.error("Failed to close output stream.", e); - } - } - } - } - - private void exportModel(KeycloakSession session, FileOutputStream outStream) throws IOException { - List realms = session.realms().getRealms(); - List reps = new ArrayList(); - for (RealmModel realm : realms) { - reps.add(ExportUtils.exportRealm(session, realm, true)); - } - Model model = new Model(); - model.setRealms(reps); - model.setModelVersion(session.realms().getMigrationModel().getStoredVersion()); - JsonSerialization.prettyMapper.writeValue(outStream, model); - } - - @Override - public FileConnectionProvider create(KeycloakSession session) { - synchronized (allProviders) { - FileConnectionProvider fcProvider = allProviders.get(session); - if (fcProvider == null) { - InMemoryModel model = new InMemoryModel(); - fcProvider = new DefaultFileConnectionProvider(this, session, model); - allProviders.put(session, fcProvider); - session.getTransaction().enlist(fcProvider); - readModelFile(session); - //logger.info("Added session " + session.hashCode() + " total sessions=" + allModels.size()); - } - - return fcProvider; - } - } - - // commitCount is used for debugging. This allows you to easily run a test - // to a particular point and then examine the JSON file. - //private static int commitCount = 0; - void commit(KeycloakSession session) { - //commitCount++; - synchronized (allProviders) { - // in case commit was somehow called twice on the same session - if (!allProviders.containsKey(session)) return; - - try { - writeModelFile(session); - } finally { - allProviders.remove(session); - //logger.info("Removed session " + session.hashCode()); - //logger.info("*** commitCount=" + commitCount); - //logger.info("commit(): Session count=" + allModels.size()); - } - - // if (commitCount == 16) {Thread.dumpStack();System.exit(0);} - } - } - - void rollback(KeycloakSession session) { - synchronized (allProviders) { - allProviders.remove(session); - //logger.info("rollback(): Session count=" + allModels.size()); - } - } - - boolean isActive(KeycloakSession session) { - synchronized (allProviders) { - return allProviders.containsKey(session); - } - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "default"; - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java b/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java deleted file mode 100644 index a3ecfeff59..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.keycloak.connections.file; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakTransaction; -import org.keycloak.provider.Provider; - -/** - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public interface FileConnectionProvider extends Provider, KeycloakTransaction { - - InMemoryModel getModel(); - - void sessionClosed(KeycloakSession session); -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java b/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java deleted file mode 100644 index 92d161a9c8..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.keycloak.connections.file; - -import org.keycloak.provider.ProviderFactory; - -/** - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public interface FileConnectionProviderFactory extends ProviderFactory { -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java b/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java deleted file mode 100644 index 5929a0ba61..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.keycloak.connections.file; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class FileConnectionSpi implements Spi { - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return "connectionsFile"; - } - - @Override - public Class getProviderClass() { - return FileConnectionProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return FileConnectionProviderFactory.class; - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java b/connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java deleted file mode 100755 index 2476b44e58..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package org.keycloak.connections.file; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; - -/** - * This class provides an in-memory copy of the entire model for each - * Keycloak session. At the start of the session, the model is read - * from JSON. When the session's transaction ends, the model is written back - * out. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class InMemoryModel { - private final Map allRealms = new HashMap(); - - // realmId, userId, userModel - private final Map> allUsers = new HashMap>(); - - private String modelVersion; - - public InMemoryModel() { - } - - public void putRealm(String id, RealmModel realm) { - allRealms.put(id, realm); - allUsers.put(id, new HashMap()); - } - - public String getModelVersion() { - return modelVersion; - } - - public void setModelVersion(String modelVersion) { - this.modelVersion = modelVersion; - } - - public RealmModel getRealm(String id) { - return allRealms.get(id); - } - - public Collection getRealms() { - return allRealms.values(); - } - - public RealmModel getRealmByName(String name) { - for (RealmModel realm : getRealms()) { - if (realm.getName().equals(name)) return realm; - } - - return null; - } - - public boolean removeRealm(String id) { - allUsers.remove(id); - return (allRealms.remove(id) != null); - } - - protected Map realmUsers(String realmId) { - Map realmUsers = allUsers.get(realmId); - if (realmUsers == null) throw new NullPointerException("Realm users not found for id=" + realmId); - return realmUsers; - } - - public void putUser(String realmId, String userId, UserModel user) { - realmUsers(realmId).put(userId, user); - } - - public UserModel getUser(String realmId, String userId) { - return realmUsers(realmId).get(userId); - } - - public boolean hasUserWithUsername(String realmId, String username) { - for (UserModel user : getUsers(realmId)) { - if (user.getUsername().equals(username)) return true; - } - - return false; - } - - public Collection getUsers(String realmId) { - return realmUsers(realmId).values(); - } - - public boolean removeUser(String realmId, String userId) { - return (realmUsers(realmId).remove(userId) != null); - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/Model.java b/connections/file/src/main/java/org/keycloak/connections/file/Model.java deleted file mode 100755 index a609cea3f7..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/Model.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.keycloak.connections.file; - -import org.keycloak.representations.idm.RealmRepresentation; - -import java.util.List; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class Model { - private String modelVersion; - private List realms; - - public String getModelVersion() { - return modelVersion; - } - - public void setModelVersion(String modelVersion) { - this.modelVersion = modelVersion; - } - - public List getRealms() { - return realms; - } - - public void setRealms(List realms) { - this.realms = realms; - } -} diff --git a/connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory b/connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory deleted file mode 100644 index d46ba7fb24..0000000000 --- a/connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.connections.file.DefaultFileConnectionProviderFactory \ No newline at end of file diff --git a/connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index b0ddd93218..0000000000 --- a/connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.connections.file.FileConnectionSpi \ No newline at end of file diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml new file mode 100755 index 0000000000..c81b062099 --- /dev/null +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml index 3010118039..2acc0bba74 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml @@ -10,4 +10,5 @@ + diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java index e011e114f3..d5bfdcbe5c 100755 --- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java @@ -1,18 +1,5 @@ package org.keycloak.connections.jpa; -import org.hibernate.ejb.AvailableSettings; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; -import org.keycloak.connections.jpa.util.JpaUtils; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; - -import javax.naming.InitialContext; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; -import javax.persistence.Persistence; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; @@ -22,10 +9,25 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import javax.naming.InitialContext; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import javax.sql.DataSource; + +import org.hibernate.ejb.AvailableSettings; +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; +import org.keycloak.connections.jpa.util.JpaUtils; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ServerInfoAwareProviderFactory; + /** * @author Stian Thorgersen */ -public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory { +public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory { private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class); diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java index 288e403335..2fc645d752 100644 --- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java @@ -1,10 +1,10 @@ package org.keycloak.connections.jpa; -import org.keycloak.provider.ServerInfoAwareProviderFactory; +import org.keycloak.provider.ProviderFactory; /** * @author Stian Thorgersen */ -public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory { +public interface JpaConnectionProviderFactory extends ProviderFactory { } diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java index 401cf74b2d..a5429e4685 100755 --- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java @@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider { public String FIRST_VERSION = "1.0.0.Final"; - public String LAST_VERSION = "1.6.1"; + public String LAST_VERSION = "1.7.0"; public String getCurrentVersionSql(String defaultSchema); diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index b8592a365c..e415488be3 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -31,6 +31,10 @@ org.keycloak.models.jpa.entities.RequiredActionProviderEntity org.keycloak.models.jpa.session.PersistentUserSessionEntity org.keycloak.models.jpa.session.PersistentClientSessionEntity + org.keycloak.models.jpa.entities.GroupEntity + org.keycloak.models.jpa.entities.GroupAttributeEntity + org.keycloak.models.jpa.entities.GroupRoleMappingEntity + org.keycloak.models.jpa.entities.UserGroupMembershipEntity org.keycloak.events.jpa.EventEntity diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index 00f338667e..67cb127a90 100755 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -1,11 +1,12 @@ package org.keycloak.connections.mongo; -import com.mongodb.DB; -import com.mongodb.MongoClient; -import com.mongodb.MongoClientOptions; -import com.mongodb.MongoClientURI; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; +import java.lang.reflect.Method; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.net.ssl.SSLSocketFactory; import org.jboss.logging.Logger; import org.keycloak.Config; @@ -15,24 +16,26 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati import org.keycloak.connections.mongo.updater.MongoUpdaterProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ServerInfoAwareProviderFactory; -import javax.net.ssl.SSLSocketFactory; -import java.lang.reflect.Method; -import java.net.UnknownHostException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; +import com.mongodb.DB; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoClientURI; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; /** * @author Stian Thorgersen */ -public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory { +public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory { // TODO Make it dynamic private String[] entities = new String[]{ "org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity", "org.keycloak.models.mongo.keycloak.entities.MongoUserEntity", "org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity", + "org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity", "org.keycloak.models.mongo.keycloak.entities.MongoClientEntity", "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity", "org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity", diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java index bce5fe4b9c..e787ce6382 100644 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java @@ -1,9 +1,9 @@ package org.keycloak.connections.mongo; -import org.keycloak.provider.ServerInfoAwareProviderFactory; +import org.keycloak.provider.ProviderFactory; /** * @author Stian Thorgersen */ -public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory { +public interface MongoConnectionProviderFactory extends ProviderFactory { } diff --git a/connections/pom.xml b/connections/pom.xml index 891cd59c65..e74e99cfaa 100755 --- a/connections/pom.xml +++ b/connections/pom.xml @@ -17,7 +17,6 @@ jpa-liquibase infinispan mongo - file mongo-update http-client diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java new file mode 100644 index 0000000000..4c18b3d143 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessCreatePresentation.java @@ -0,0 +1,36 @@ +package org.keycloak.representations.idm; + +/** + * @author Stian Thorgersen + */ +public class ClientInitialAccessCreatePresentation { + + private Integer expiration; + + private Integer count; + + public ClientInitialAccessCreatePresentation() { + } + + public ClientInitialAccessCreatePresentation(Integer expiration, Integer count) { + this.expiration = expiration; + this.count = count; + } + + public Integer getExpiration() { + return expiration; + } + + public void setExpiration(Integer expiration) { + this.expiration = expiration; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java new file mode 100644 index 0000000000..d8021ad9f9 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientInitialAccessPresentation.java @@ -0,0 +1,67 @@ +package org.keycloak.representations.idm; + +/** + * @author Stian Thorgersen + */ +public class ClientInitialAccessPresentation { + + private String id; + + private String token; + + private Integer timestamp; + + private Integer expiration; + + private Integer count; + + private Integer remainingCount; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Integer getTimestamp() { + return timestamp; + } + + public void setTimestamp(Integer timestamp) { + this.timestamp = timestamp; + } + + public Integer getExpiration() { + return expiration; + } + + public void setExpiration(Integer expiration) { + this.expiration = expiration; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + public Integer getRemainingCount() { + return remainingCount; + } + + public void setRemainingCount(Integer remainingCount) { + this.remainingCount = remainingCount; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index 099950512b..514c0fb953 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -19,6 +19,7 @@ public class ClientRepresentation { protected Boolean enabled; protected String clientAuthenticatorType; protected String secret; + protected String registrationAccessToken; protected String[] defaultRoles; protected List redirectUris; protected List webOrigins; @@ -124,6 +125,14 @@ public class ClientRepresentation { this.secret = secret; } + public String getRegistrationAccessToken() { + return registrationAccessToken; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } + public List getRedirectUris() { return redirectUris; } @@ -251,4 +260,5 @@ public class ClientRepresentation { public void setProtocolMappers(List protocolMappers) { this.protocolMappers = protocolMappers; } + } diff --git a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java new file mode 100755 index 0000000000..4414f240af --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java @@ -0,0 +1,85 @@ +package org.keycloak.representations.idm; + +import org.codehaus.jackson.annotate.JsonIgnore; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class GroupRepresentation { + protected String id; + protected String name; + protected String path; + protected Map> attributes; + protected List realmRoles; + protected Map> clientRoles; + protected List subGroups; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List getRealmRoles() { + return realmRoles; + } + + public void setRealmRoles(List realmRoles) { + this.realmRoles = realmRoles; + } + + public Map> getClientRoles() { + return clientRoles; + } + + public void setClientRoles(Map> clientRoles) { + this.clientRoles = clientRoles; + } + + + public Map> getAttributes() { + return attributes; + } + + public void setAttributes(Map> attributes) { + this.attributes = attributes; + } + + public GroupRepresentation singleAttribute(String name, String value) { + if (this.attributes == null) attributes = new HashMap<>(); + attributes.put(name, Arrays.asList(value)); + return this; + } + + public List getSubGroups() { + return subGroups; + } + + public void setSubGroups(List subGroups) { + this.subGroups = subGroups; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java index 1e74002bc8..0dc7f0a019 100755 --- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java @@ -46,12 +46,14 @@ public class IdentityProviderRepresentation { * @see #UPFLM_MISSING * @see #UPFLM_OFF */ + @Deprecated protected String updateProfileFirstLoginMode = UPFLM_ON; protected boolean trustEmail; protected boolean storeToken; protected boolean addReadTokenRoleOnCreate; protected boolean authenticateByDefault; + protected String firstBrokerLoginFlowAlias; protected Map config = new HashMap(); public String getInternalId() { @@ -106,15 +108,17 @@ public class IdentityProviderRepresentation { } /** - * @return see {@link #updateProfileFirstLoginMode} + * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator */ + @Deprecated public String getUpdateProfileFirstLoginMode() { return updateProfileFirstLoginMode; } /** - * @param updateProfileFirstLoginMode see {@link #updateProfileFirstLoginMode} + * @deprecated deprecated and replaced by configuration on IdpReviewProfileAuthenticator */ + @Deprecated public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) { this.updateProfileFirstLoginMode = updateProfileFirstLoginMode; } @@ -127,6 +131,14 @@ public class IdentityProviderRepresentation { this.authenticateByDefault = authenticateByDefault; } + public String getFirstBrokerLoginFlowAlias() { + return firstBrokerLoginFlowAlias; + } + + public void setFirstBrokerLoginFlowAlias(String firstBrokerLoginFlowAlias) { + this.firstBrokerLoginFlowAlias = firstBrokerLoginFlowAlias; + } + public boolean isStoreToken() { return this.storeToken; } diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 387ab7e783..00785cf710 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -47,7 +47,9 @@ public class RealmRepresentation { protected String certificate; protected String codeSecret; protected RolesRepresentation roles; + protected List groups; protected List defaultRoles; + protected List defaultGroups; @Deprecated protected Set requiredCredentials; protected String passwordPolicy; @@ -268,6 +270,14 @@ public class RealmRepresentation { this.defaultRoles = defaultRoles; } + public List getDefaultGroups() { + return defaultGroups; + } + + public void setDefaultGroups(List defaultGroups) { + this.defaultGroups = defaultGroups; + } + public String getPrivateKey() { return privateKey; } @@ -775,4 +785,12 @@ public class RealmRepresentation { public void setClientAuthenticationFlow(String clientAuthenticationFlow) { this.clientAuthenticationFlow = clientAuthenticationFlow; } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java index 07865db1a8..8635014c11 100755 --- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java @@ -40,6 +40,8 @@ public class UserRepresentation { @Deprecated protected List socialLinks; + protected List groups; + public String getSelf() { return self; } @@ -216,4 +218,12 @@ public class UserRepresentation { public void setServiceAccountClientId(String serviceAccountClientId) { this.serviceAccountClientId = serviceAccountClientId; } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRoleMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRoleMappingRepresentation.java deleted file mode 100755 index a1a1e1c21e..0000000000 --- a/core/src/main/java/org/keycloak/representations/idm/UserRoleMappingRepresentation.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.keycloak.representations.idm; - -import java.util.HashSet; -import java.util.Set; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class UserRoleMappingRepresentation { - protected String self; // link - protected String username; - protected Set roles; - - public String getSelf() { - return self; - } - - public void setSelf(String self) { - this.self = self; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public Set getRoles() { - return roles; - } - - public void setRoles(Set roles) { - this.roles = roles; - } - - public UserRoleMappingRepresentation role(String role) { - if (this.roles == null) this.roles = new HashSet(); - this.roles.add(role); - return this; - } - -} diff --git a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java new file mode 100644 index 0000000000..1ff32fc096 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java @@ -0,0 +1,223 @@ +package org.keycloak.representations.oidc; + +import org.codehaus.jackson.annotate.JsonAutoDetect; + +import java.util.List; + +/** + * @author Stian Thorgersen + */ +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class OIDCClientRepresentation { + + private List redirect_uris; + + private String token_endpoint_auth_method; + + private String grant_types; + + private String response_types; + + private String client_id; + + private String client_secret; + + private String client_name; + + private String client_uri; + + private String logo_uri; + + private String scope; + + private String contacts; + + private String tos_uri; + + private String policy_uri; + + private String jwks_uri; + + private String jwks; + + private String software_id; + + private String software_version; + + private Integer client_id_issued_at; + + private Integer client_secret_expires_at; + + private String registration_client_uri; + + private String registration_access_token; + + public List getRedirectUris() { + return redirect_uris; + } + + public void setRedirectUris(List redirectUris) { + this.redirect_uris = redirectUris; + } + + public String getTokenEndpointAuthMethod() { + return token_endpoint_auth_method; + } + + public void setTokenEndpointAuthMethod(String token_endpoint_auth_method) { + this.token_endpoint_auth_method = token_endpoint_auth_method; + } + + public String getGrantTypes() { + return grant_types; + } + + public void setGrantTypes(String grantTypes) { + this.grant_types = grantTypes; + } + + public String getResponseTypes() { + return response_types; + } + + public void setResponseTypes(String responseTypes) { + this.response_types = responseTypes; + } + + public String getClientId() { + return client_id; + } + + public void setClientId(String clientId) { + this.client_id = clientId; + } + + public String getClientSecret() { + return client_secret; + } + + public void setClientSecret(String clientSecret) { + this.client_secret = clientSecret; + } + + public String getClientName() { + return client_name; + } + + public void setClientName(String client_name) { + this.client_name = client_name; + } + + public String getClientUri() { + return client_uri; + } + + public void setClientUri(String client_uri) { + this.client_uri = client_uri; + } + + public String getLogoUri() { + return logo_uri; + } + + public void setLogoUri(String logo_uri) { + this.logo_uri = logo_uri; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getContacts() { + return contacts; + } + + public void setContacts(String contacts) { + this.contacts = contacts; + } + + public String getTosUri() { + return tos_uri; + } + + public void setTosUri(String tos_uri) { + this.tos_uri = tos_uri; + } + + public String getPolicyUri() { + return policy_uri; + } + + public void setPolicyUri(String policy_uri) { + this.policy_uri = policy_uri; + } + + public String getJwksUri() { + return jwks_uri; + } + + public void setJwksUri(String jwks_uri) { + this.jwks_uri = jwks_uri; + } + + public String getJwks() { + return jwks; + } + + public void setJwks(String jwks) { + this.jwks = jwks; + } + + public String getSoftwareId() { + return software_id; + } + + public void setSoftwareId(String softwareId) { + this.software_id = softwareId; + } + + public String getSoftwareVersion() { + return software_version; + } + + public void setSoftwareVersion(String softwareVersion) { + this.software_version = softwareVersion; + } + + public Integer getClientIdIssuedAt() { + return client_id_issued_at; + } + + public void setClientIdIssuedAt(Integer clientIdIssuedAt) { + this.client_id_issued_at = clientIdIssuedAt; + } + + public Integer getClientSecretExpiresAt() { + return client_secret_expires_at; + } + + public void setClientSecretExpiresAt(Integer client_secret_expires_at) { + this.client_secret_expires_at = client_secret_expires_at; + } + + public String getRegistrationClientUri() { + return registration_client_uri; + } + + public void setRegistrationClientUri(String registrationClientUri) { + this.registration_client_uri = registrationClientUri; + } + + public String getRegistrationAccessToken() { + return registration_access_token; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registration_access_token = registrationAccessToken; + } + +} diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java index 0d103a6b9b..10b6c20ae6 100644 --- a/core/src/main/java/org/keycloak/util/TokenUtil.java +++ b/core/src/main/java/org/keycloak/util/TokenUtil.java @@ -19,6 +19,7 @@ public class TokenUtil { public static final String TOKEN_TYPE_OFFLINE = "Offline"; + public static boolean isOfflineTokenRequested(String scopeParam) { if (scopeParam == null) { return false; diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index 81ce0957c5..ad786a3cc0 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -36,10 +36,6 @@ org.keycloak keycloak-model-jpa - - org.keycloak - keycloak-model-file - org.keycloak keycloak-model-sessions-infinispan diff --git a/distribution/docs-dist/assembly.xml b/distribution/docs-dist/assembly.xml index 00862c28ea..7da1a5c042 100755 --- a/distribution/docs-dist/assembly.xml +++ b/distribution/docs-dist/assembly.xml @@ -9,7 +9,7 @@ - ../../target/site/apidocs + target/site/apidocs javadocs diff --git a/distribution/docs-dist/src/index.html b/distribution/docs-dist/src/index.html index f196cd56ac..6aeffbed71 100755 --- a/distribution/docs-dist/src/index.html +++ b/distribution/docs-dist/src/index.html @@ -1,11 +1,33 @@ + + + + + + +

Keyloak Documentation

- \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/assembly.xml b/distribution/feature-packs/server-feature-pack/assembly.xml index 1e142ae9b4..4cd92ceff8 100644 --- a/distribution/feature-packs/server-feature-pack/assembly.xml +++ b/distribution/feature-packs/server-feature-pack/assembly.xml @@ -66,4 +66,15 @@ + + + + src/main/resources/content/standalone/configuration/keycloak-server.json + content/domain/servers/server-one/configuration + + + src/main/resources/content/standalone/configuration/keycloak-server.json + content/domain/servers/server-two/configuration + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml index 2dde48f173..582aa42e8e 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/configuration/domain/subsystems.xml @@ -10,7 +10,7 @@ ee.xmlejb3.xmlio.xml - infinispan.xml + keycloak-infinispan.xmljaxrs.xmljca.xmljdr.xml @@ -41,7 +41,7 @@ ee.xmlejb3.xmlio.xml - infinispan.xml + keycloak-infinispan.xmljaxrs.xmljca.xmljdr.xml @@ -74,7 +74,7 @@ ee.xmlejb3.xmlio.xml - infinispan.xml + keycloak-infinispan.xmliiop-openjdk.xmljaxrs.xmljca.xml @@ -108,7 +108,7 @@ ee.xmlejb3.xmlio.xml - infinispan.xml + keycloak-infinispan.xmliiop-openjdk.xmljaxrs.xmljca.xml diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml deleted file mode 100755 index 5400a883d3..0000000000 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml deleted file mode 100755 index 2612e06451..0000000000 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml index c339f44671..7e61fb4bd6 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml @@ -8,7 +8,6 @@ - @@ -33,7 +32,6 @@ - @@ -70,4 +68,4 @@ - \ No newline at end of file + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml index 77ce3ad8dc..340c3bff4c 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml @@ -18,7 +18,6 @@ - @@ -43,7 +42,6 @@ - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml index 60ccb65be0..9f60836a8f 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml @@ -173,10 +173,6 @@ - - - - @@ -224,11 +220,11 @@ - + - + @@ -250,12 +246,6 @@ - - - - - - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml index c339f44671..7e61fb4bd6 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml @@ -8,7 +8,6 @@ - @@ -33,7 +32,6 @@ - @@ -70,4 +68,4 @@ - \ No newline at end of file + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml deleted file mode 100755 index a881b2b672..0000000000 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml deleted file mode 100755 index 46f8ffd25d..0000000000 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml index 44703f8a5d..aa895e8b66 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml @@ -18,7 +18,6 @@ - @@ -43,7 +42,6 @@ - diff --git a/docbook/auth-server-docs/pom.xml b/docbook/auth-server-docs/pom.xml index a7c2ddd33f..546b18d5e6 100755 --- a/docbook/auth-server-docs/pom.xml +++ b/docbook/auth-server-docs/pom.xml @@ -114,6 +114,10 @@ picketlink.version ${picketlink.version} + + wildfly.version + ${wildfly.version} + saxon diff --git a/docbook/auth-server-docs/reference/en/en-US/master.xml b/docbook/auth-server-docs/reference/en/en-US/master.xml index 2af744f8e9..16a8601228 100755 --- a/docbook/auth-server-docs/reference/en/en-US/master.xml +++ b/docbook/auth-server-docs/reference/en/en-US/master.xml @@ -27,6 +27,7 @@ + @@ -48,6 +49,7 @@ + ]> @@ -116,6 +118,7 @@ This one is short &MultiTenancy; &JAAS; + &ClientRegistration; &IdentityBroker; &Themes; @@ -131,6 +134,7 @@ This one is short &AccessTypes; &Roles; + &Groups; &DirectAccess; &ServiceAccounts; &CORS; diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml index 650ed769bd..b0a443dd41 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -79,16 +79,34 @@
Version specific migration +
+ Migrating to 1.7.0.CR1 + + Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator + + In this version, we added First Broker Login, which allows you to specify what exactly should be done + when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user + yet linked to the social account. As part of this work, we added option First Login Flow to identity providers where + you can specify the flow and then you can configure this flow under Authentication tab in admin console. + + + We also removed the option Update Profile On First Login from the Identity provider settings and moved it + to the configuration of Review Profile authenticator. So once you specify which flow should be used for your + Identity provider (by default it's First Broker Login flow), you go to Authentication tab, select the flow + and then you configure the option under Review Profile authenticator. + + +
Migrating to 1.6.0.Final - Refresh tokens are not reusable anymore + Option that refresh tokens are not reusable anymore - Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak no longer permits - this by default. When a refresh token is used to obtain a new access token a new refresh token is also - included. This new refresh token should be used next time the access token is refreshed. If this is - a problem for you it's possible to enable reuse of refresh tokens in the admin console under token - settings. + Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak still permits this, + but also have an option Revoke refresh token to disallow it. Option is in in admin console under token settings. + When a refresh token is used to obtain a new access token a new refresh token is also + included. When option is enabled, then this new refresh token should be used next time the access token is refreshed. + It won't be possible to reuse old refresh token multiple times. diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml index 833d3901bf..678c6d13f7 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/admin-permissions.xml @@ -63,6 +63,9 @@ manage-applications - Create, modify and delete applications in the realm + + create-clients - Create clients in the realm + manage-clients - Create, modify and delete clients in the realm diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml index 10cb89db71..12e2b1ac78 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml @@ -866,6 +866,14 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
+
+ Modifying First Broker Login Flow + + First Broker Login flow is used during first login with some identity provider. Term First Login means that there is not yet existing Keycloak account + linked with the particular authenticated identity provider account. More details about this flow are in the Identity provider chapter. + +
+
Authentication of clients Keycloak actually supports pluggable authentication for OpenID Connect diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml new file mode 100755 index 0000000000..fab7119e4f --- /dev/null +++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml @@ -0,0 +1,215 @@ + + Client Registration + + + In order for an application or service to utilize Keycloak it has to register a client in Keycloak. An + admin can do this through the admin console (or admin REST endpoints), but clients can also register themselves + through Keycloak's client registration service. + + + + The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect + Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers + if required. The Client Registration Service endpoint is <KEYCLOAK URL>/realms/<realm>/clients/<provider>. + + + The built-in supported providers are: + + default Keycloak Representations + install Keycloak Adapter Configuration + openid-connect OpenID Connect Dynamic Client Registration + saml2-entity-descriptor SAML Entity Descriptors + + The following sections will describe how to use the different providers. + + +
+ Authentication + + To invoke the Client Registration Services you need a token. The token can be a standard bearer token, a + initial access token or a registration access token. + + +
+ Bearer Token + + The bearertoken can be issued on behalf of a user or a Service Account. The following permissions are required + to invoke the endpoints (see Admin Permissions for more details): + + + create-client or manage-client - To create clients + + + view-client or manage-client - To view clients + + + manage-client - To update or delete clients + + + If you are using a regular bearer token to create clients we recommend using a token from on behalf of a + Service Account with only the create-client role. See the + Service Account section for more details. + +
+ +
+ Initial Access Token + + The best approach to create new clients is by using initial access tokens. An initial access token can + only be used to create clients and has a configurable expiration as well as a configurable limit on + how many clients can be created. + + + An initial access token can be created through the admin console. To create a new initial access token + first select the realm in the admin console, then click on Realm Settings in the menu + on the left, followed by Initial Access Tokens in the tabs displayed in the page. + + + You will now be able to see any existing initial access tokens. If you have access you can delete tokens + that are no longer required. You can only retrieve the value of the token when you are creating it. To + create a new token click on Create. You can now optionally add how long the token + should be valid, also how many clients can be created using the token. After you click on Save + the token value is displayed. It is important that you copy/paste this token now as you won't be able + to retrieve it later. If you forget to copy/paste it, then delete the token and create another one. + The token value is used as a standard bearer token when invoking the Client Registration Services, by + adding it to the Authorization header in the request. For example: + + +
+ +
+ Registration Access Token + + When you create a client through the Client Registration Service the response will include a registration + access token. The registration access token provides access to retrieve the client configuration later, but + also to update or delete the client. The registration access token is included with the request in the + same way as a bearer token or initial access token. Registration access tokens are only valid once + when it's used the response will include a new token. + + + If a client was created outside of the Client Registration Service it won't have a registration access + token associated with it. You can create one through the admin console. This can also be useful if + you loose the token for a particular client. To create a new token find the client in the admin console + and click on Credentials. Then click on Generate registration access token. + +
+
+ +
+ Keycloak Representations + + The default client registration provider can be used to create, retrieve, update and delete a client. It uses + Keycloaks Client Representation format which provides support for configuring clients exactly as they can + be configured through the admin console, including for example configuring protocol mappers. + + + To create a client create a Client Representation (JSON) then do a HTTP POST to: + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/default. It will return a Client Representation + that also includes the registration access token. You should save the registration access token somewhere + if you want to retrieve the config, update or delete the client later. + + + To retrieve the Client Representation then do a HTTP GET to: + <KEYCLOAK URL>/realms/<realm>clients/<provider>/default/<client id>. It will also + return a new registration access token. + + + To update the Client Representation then do a HTTP PUT to with the updated Client Representation to: + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/default/<client id>. It will also + return a new registration access token. + + + To delete the Client Representation then do a HTTP DELETE to: + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/default/<client id> + +
+ +
+ Keycloak Adapter Configuration + + The installation client registration provider can be used to retrieve the adapter configuration + for a client. In addition to token authentication you can also authenticate with client credentials using + HTTP basic authentication. To do this include the following header in the request: + + + + To retrieve the Adapter Configuration then do a HTTP GET to: + <KEYCLOAK URL>//realms/<realm>clients/<provider>/installation/<client id> + + + No authentication is required for public clients. This means that for the JavaScript adapter you can + load the client configuration directly from Keycloak using the above URL. + +
+ +
+ OpenID Connect Dynamic Client Registration + + Keycloak implements OpenID Connect Dynamic Client Registration, + which extends OAuth 2.0 Dynamic Client Registration Protocol and + OAuth 2.0 Dynamic Client Registration Management Protocol. + + + The endpoint to use these specifications to register clients in Keycloak is: + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/oidc[/<client id>]. + + + This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm: + <KEYCLOAK URL>/realms/<realm>/.well-known/openid-configuration. + +
+ +
+ SAML Entity Descriptors + + The SAML Entity Descriptor endpoint only supports using SAML v2 Entity Descriptors to create clients. It + doesn't support retrieving, updating or deleting clients. For those operations the Keycloak representation + endpoints should be used. When creating a client a Keycloak Client Representation is returned with details + about the created client, including a registration access token. + + + To create a client do a HTTP POST with the SAML Entity Descriptor to: + <KEYCLOAK URL>/realms/<realm>/clients/<provider>/saml2-entity-descriptor. + +
+ +
+ Client Registration Java API + + The Client Registration Java API makes it easy to use the Client Registration Service using Java. To use + include the dependency org.keycloak:keycloak-client-registration-api:>VERSION< from + Maven. + + + For full instructions on using the Client Registration refer to the JavaDocs. Below is an example of creating + a client: + + +
+ + + +
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml index 7aefdb676d..49d58ad945 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml @@ -8,7 +8,7 @@ - Create a new theme within the themes/admin/mytheme directory in your distribution. + Create a new theme within the themes/mytheme/admin directory in your distribution. Where mytheme is whatever you want to name your theme. @@ -19,15 +19,15 @@ import=common/keycloak ]]> - Copy the file themes/admin/base/resources/partials/user-attribute-entry.html into the - a mirror directory in your theme: themes/admin/mytheme/resources/partials/user-attribute-entry.html. + Copy the file themes/base/admin/resources/partials/user-attributes.html into the + a mirror directory in your theme: themes/mytheme/admin/resources/partials/user-attributes.html. What you are doing here is overriding the user attribute entry page in the admin console and putting in what attributes you want. This file already contains an example of entering address data. You can remove this if you want and replace it with something else. Also, if you want to edit this file directly instead of creating a new theme, you can. - In the user-attribute-entry.html file add your custom user attribute entry form item. For example + In the user-attributes.html file add your custom user attribute entry form item. For example
@@ -52,7 +52,7 @@ import=common/keycloak - Create a new theme within the themes/login/mytheme directory in your distribution. + Create a new theme within the themes/mytheme/login directory in your distribution. Where mytheme is whatever you want to name your theme. @@ -63,8 +63,8 @@ import=common/keycloak styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.css ../patternfly/lib/zocial/zocial.css css/login.css]]> - Copy the file themes/login/base/register.ftl into the - a mirror directory in your theme: themes/login/mytheme/register.ftl. + Copy the file themes/base/login/register.ftl into the + a mirror directory in your theme: themes/mytheme/login/register.ftl. What you are doing here is overriding the registration page and adding what attributes you want. This file already contains an example of entering address data. You can remove this if you want and replace it with something else. Also, if you want to edit this file directly instead @@ -101,7 +101,7 @@ styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login. - Create a new theme within the themes/account/mytheme directory in your distribution. + Create a new theme within the themes/mytheme/account directory in your distribution. Where mytheme is whatever you want to name your theme. @@ -113,8 +113,8 @@ import=common/keycloak styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]> - Copy the file themes/account/base/account.ftl into the - a mirror directory in your theme: themes/account/mytheme/account.ftl. + Copy the file themes/base/account/account.ftl into the + a mirror directory in your theme: themes/mytheme/account/account.ftl. What you are doing here is overriding the profile page and adding what attributes you want to manage. This file already contains an example of entering address data. You can remove this if you want and replace it with something else. Also, if you want to edit this file directly instead diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/groups.xml b/docbook/auth-server-docs/reference/en/en-US/modules/groups.xml new file mode 100755 index 0000000000..eb54e0e86f --- /dev/null +++ b/docbook/auth-server-docs/reference/en/en-US/modules/groups.xml @@ -0,0 +1,31 @@ + + Groups + + Groups in Keycloak allow you to manage a common set of attributes and role mappings for a large set of users. + Users can be members of zero or more groups. Users inherit the attributes and role mappings assign to each group. + As an admin this makes it easy for you to manage permissions for a user in one place. + + + Groups are hierarchical. A group can have many subgroups, but a group can only have one parent. Subgroups inherit + the attributes and role mappings from the parent. This applies to user as well. So, if you have a parent group and a child group + and a user that only belongs to the child group, the user inherits the attributes and role mappings of both the + parent and child. + +
+ Groups vs. Roles + + In the IT world the concepts of Group and Role are often blurred and interchangeable. In Keycloak, Groups are just + a collection of users that you can apply roles and attributes to in one place. Roles are used to assign permissions + and access control. + + + Keycloak Roles have the concept of a Composite Role. A role can be associated with one or more additional roles. + This is called a Composite Role. If a user has a role mapping to the Composite Role, they inherit all the roles associated + with the composite. So what's the difference from a Keycloak Group and a Composite Role? Logically they could be + used for the same exact thing. The difference is conceptual. Composite roles should be used to compose the + permission model of your set of services and applications. So, roles become a set of permissions. Groups on the + other hand, would be a set of users that have a set of permissions. Use Groups to manage users, composite roles to + manage applications and services. + +
+
\ No newline at end of file diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml index a262b850fe..41c36f0c4f 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml @@ -66,7 +66,7 @@ -
+
Overview @@ -127,10 +127,11 @@ Now Keycloak is going to check if the response from the identity provider is valid. If valid, it will create an user - or just skip that if the user already exists. If it is a new user, Keycloak will ask informations about the user to the identity provider + or just skip that if the user already exists. If it is a new user, Keycloak may ask informations about the user to the identity provider (or just read that from a security token) and create the user locally. This is what we call identity federation. - If the user already exists Keycloak will ask him to link the identity returned from the identity provider - with his existing account. A process that we call account linking. + If the user already exists Keycloak may ask him to link the identity returned from the identity provider + with his existing account. A process that we call account linking. What exactly is done is configurable + and can be specified by setup of First Login Flow . At the end of this step, Keycloak authenticates the user and issues its own token in order to access the requested resource in the service provider. @@ -210,7 +211,7 @@ Social providers allows you to enable social authentication to your realm. Keycloak makes it easy to let users log in to your application using an existing account with a social network. - Currently Facebook, Google and Twitter are supported with more planned for the future. + Currently Facebook, Google, Twitter, GitHub, LinkedIn and StackOverflow are supported with more planned for the future. @@ -274,6 +275,15 @@ be used by any other means. + + + Authenticate By Default + + + If enabled, Keycloak will automatically redirect to this identity provider even before displaying login screen. + In other words, steps 3 and 4 from the base flow are skipped. + + Store Tokens @@ -293,20 +303,6 @@ to access any stored external tokens via the broker service. - - - Update Profile on First Login - - - Allows you to force users to update their profile right after the authentication finishes and - before the account is actually created in Keycloak. When "On", users will be always presented with the - update profile page asking for additional information in order to federate their identities. - When "On missing info", users will be presented with the update profile page only if some - mandatory information (email, first name, last name) is not provided by identity provider. - If "Off", the account will be created with the minimal information obtained from the identity provider - during the authentication process. - - Trust email @@ -326,6 +322,16 @@ You can put number into this field, providers with lower numbers are shown first. + + + First Login Flow + + + Alias of authentication flow, which is triggered during 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. + More details in First Login section. + +
Server and Keycloak Adapter UserguideHTMLHTML Single PagePDF
SAML Client Adapter UserguideHTMLHTML Single PagePDF
Admin REST APIHTML
JavadocsHTML
@@ -340,8 +346,8 @@ Forcing users to register to your realm when they want to access applications is hard. So is trying to remember yet another username and password combination. Social identity providers makes it easy for users to register on your realm and quickly sign in using a social network. - Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter and - even Github. + Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter, + Github, LinkedId and StackOverflow.
@@ -1211,7 +1217,13 @@ Authorization: Bearer {keycloak_access_token}]]>
Automatically Select and Identity Provider - Applications can automatically select an identity provider in order to authenticate an user. In this case, the user will not be presented to the login page but automatically redirected to the identity provider. + Each Identity provider has option Authenticate By Default, which allows that Identity provider is automatically + selected during authentication. User won't even see Keycloak login page, but is automatically redirected to the identity provider. + + + Applications can also automatically select an identity provider in order to authenticate an user. + Selection per application is preferred over Authenticate By Default option if you need more control + on when exactly is Identity provider automatically selected. Keycloak supports a specific HTTP query parameter that you can use as a hint to tell the server which identity provider should be used to authenticate the user. @@ -1283,6 +1295,122 @@ keycloak.createLoginUrl({
+
+ First Login Flow + + When Keycloak successfully authenticates user through identity provider (step 8 in Overview chapter), + there can be two situations: + + + + There is already Keycloak user account linked with the authenticated identity provider account. In this case, + Keycloak will just authenticate as the existing user and redirect back to application (step 9 in Overview chapter). + + + + + There is not yet existing Keycloak user account linked with the identity provider account. This situation is more tricky. + Usually you just want to register new account into Keycloak database, but what if there is existing Keycloak account with same email like the identity provider account? + Automatically link identity provider account with existing Keycloak account is not very good option as there are possible security flaws related to that... + + + + + + Because we had various requirements what to do in second case, we changed the behaviour to be flexible and configurable + through Authentication Flows SPI. In admin console in Identity provider settings, there is option + First Login Flow, which allows you to choose, which workflow will be used after "first login" with this identity provider account. + By default it points to first broker login flow, but you can configure and use your own flow and use different flows for different identity providers etc. + + + The flow itself is configured in admin console under Authentication tab. When you choose First Broker Login flow, + you will see what authenticators are used by default. You can either re-configure existing flow (For example disable some authenticators, + mark some of them as required, configure some authenticators etc). Or you can even create new authentication flow and/or + write your own Authenticator implementations and use it in your flow. See Authentication Flows SPI for more details on how to do it. + + + For First Broker Login case, it might be useful if your Authenticator is subclass of org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator + so you have access to all details about authenticated Identity provider account. But it's not a requirement. + +
+ Default First Login Flow + + Let's describe the default behaviour provided by First Broker Login flow. There are those authenticators: + + + Review Profile + + + This authenticator might display the profile info page, where user can review his profile retrieved from identity provider. + The authenticator is configurable. You can set Update Profile On First Login option. + When On, users will be always presented with the profile page asking for additional information + in order to federate their identities. When missing, users will be presented with + the profile page only if some mandatory information (email, first name, last name) is not provided by identity provider. + If Off, the profile page won't be displayed, unless user clicks in later phase on Review profile info + link (page displayed in later phase by Confirm Link Existing Account authenticator) + + + + + + Create User If Unique + + + This authenticator checks if there is already existing Keycloak account with same email or username like + the account from identity provider. If it's not, then authenticator just creates new Keyclok account and + link it with identity provider and whole flow is finished. Otherwise it goes to the next Handle Existing Account subflow. + If you always want to ensure that there is no duplicated account, you can mark this authenticator as REQUIRED . + In this case, the user will see the error page if there is existing Keycloak account and user needs + to link his identity provider account through Account management. + + + This authenticator also has config option Require Password Update After Registration . + When enabled, user is required to update password after account is created. + + + + + + Confirm Link Existing Account + + + User will see the info page, that there is existing Keycloak account with same email. He can either + review his profile again and use different email or username (flow is restarted and goes back to Review Profile authenticator). + Or he can confirm that he wants to link identity provider account with his existing Keycloak account. + Disable this authenticator if you don't want users to see this confirmation page, but go straight + to linking identity provider account by email verification or re-authentication. + + + + + + Verify Existing Account By Email + + + This authenticator is ALTERNATIVE by default, so it's used only if realm has SMTP setup configured. + It will send mail to user, where he can confirm that he wants to link identity provider with his Keycloak account. + Disable this if you don't want to confirm linking by email, but instead you always want users to reauthenticate with their password (and alternatively OTP). + + + + + + Verify Existing Account By Re-authentication + + + This authenticator is used if email authenticator is disabled or non-available (SMTP not configured for realm). It + will display login screen where user needs to authenticate with his password to link his Keycloak account with Identity provider. + User can also re-authenticate with some different identity provider, which is already linked to his keycloak account. + You can also force users to use OTP, otherwise it's optional and used only if OTP is already set for user account. + + + + + + +
+
+
Examples diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml b/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml index eda5acacc4..71c2919441 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/providers.xml @@ -16,7 +16,7 @@ For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory: - +]]> The example uses an imagined MaxList which has a maximum size and is concurrency safe. When the maximum size is reached and new entries are added the oldest entry is removed. Keycloak creates a single instance of EventListenerProviderFactory which makes it possible to store state for multiple requests. EventListenerProvider @@ -51,7 +51,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact Next you would implement EventListenerProvider: - +]]> The file META-INF/services/org.keycloak.events.EventListenerProviderFactory should contain the full name of your ProviderFactory implementation: + +
+ Show info from you SPI implementation in Keycloak admin console + + Sometimes it is useful to show additional info about your Provider to a Keycloak administrator. + You can show provider build time informations (eg. version of custom provider currently installed), + current configuration of the provider (eg. url of remote system your provider talks to) or some operational + info (average time of response from remote system your provider talks to). + Keycloak admin console provides Server Info page to show this kind of information. + + + To show info from your provider it is enough to implement + org.keycloak.provider.ServerInfoAwareProviderFactory interface in your ProviderFactory. + Example implementation for MyEventListenerProviderFactory from previous example: + events; + private int max; + +... + + @Override + public void init(Config.Scope config) { + max = config.getInt("max"); + events = new MaxList(max); + } + +... + + @Override + public Map getOperationalInfo() { + Map ret = new LinkedHashMap<>(); + ret.put("version", "1.0"); + ret.put("listSizeMax", max + ""); + ret.put("listSizeCurrent", events.size() + ""); + return ret; + } + +} +]]> + + +
@@ -98,13 +146,13 @@ public class MyEventListenerProvider implements EventListenerProvider { To register a provider using Modules first create a module. To do this you can either use the jboss-cli script or manually create a folder inside KEYCLOAK_HOME/modules and add your jar and a module.xml. For example to add the event listener sysout example provider using the jboss-cli script execute: - +]]> Or to manually create it start by creating the folder KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main. Then copy event-listener-sysout-example.jar to this folder and create module.xml with the following content: - @@ -116,7 +164,7 @@ public class MyEventListenerProvider implements EventListenerProvider { -}]]> +]]> Once you've created the module you need to register this module with Keycloak. This is done by editing diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml b/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml index 9cd6176cc1..b7c034c70b 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/roles.xml @@ -1,7 +1,7 @@ Roles - In Keycloak, roles (or permissions) can be defined globally at the realm level, or individually per application. + In Keycloak, roles can be defined globally at the realm level, or individually per application. Each role has a name which must be unique at the level it is defined in, i.e. you can have only one "admin" role at the realm level. You may have that a role named "admin" within an Application too, but "admin" must be unique for that application. diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml index 558f943f12..78d9a4b3d6 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml @@ -43,9 +43,9 @@
- Install on existing WildFly 9.0.1.Final + Install on existing WildFly &wildfly.version; - Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download + Keycloak can be installed into an existing WildFly &wildfly.version; server. To do this download keycloak-overlay-&project.version;.zip or keycloak-overlay-&project.version;.tar.gz. Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak run: @@ -62,11 +62,15 @@ To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with the desired server-config. If you are running the server in standalone mode run: - cd <WILDFLY_HOME>/bin - ./jboss-cli.sh -c --file=keycloak-install.cli + +cd <WILDFLY_HOME>/bin +./jboss-cli.sh -c --file=keycloak-install.cli + Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run: - cd <WILDFLY_HOME>/bin - ./jboss-cli.sh -c --file=keycloak-install-ha.cli + +cd <WILDFLY_HOME>/bin +./jboss-cli.sh -c --file=keycloak-install-ha.cli + You may see exceptions in the server log, but after restarting the server they should be gone. You can restart the server with: <WILDFLY_HOME>/bin/jboss-cli.sh -c :reload @@ -75,7 +79,7 @@
Install on existing JBoss EAP 6.4.0.GA - Same procedure as WildFly 9.0.1.Final, but download keycloak-overlay-eap6-&project.version;.zip or keycloak-overlay-eap6-&project.version;.tar.gz. + Same procedure as WildFly &wildfly.version;, but download keycloak-overlay-eap6-&project.version;.zip or keycloak-overlay-eap6-&project.version;.tar.gz.
@@ -85,7 +89,7 @@ To install it first download keycloak-demo-&project.version;.zip or keycloak-demo-&project.version;.tar.gz. Once downloaded extract it inside keycloak-demo-&project.version; you'll find keycloak which contains - a full WildFly 9.0.0.Final server with Keycloak Server and Adapters included. You'll also find docs + a full WildFly &wildfly.version; server with Keycloak Server and Adapters included. You'll also find docs and examples which contains everything you need to get started developing applications that use Keycloak. @@ -437,12 +441,12 @@ All configuration options are optional. Default value for directory is settings you can specify before boot time. This is configured in the standalone/configuration/keycloak-server.json. By default the setting is like this: - Possible configuration options are: @@ -659,25 +663,25 @@ All configuration options are optional. Default value for directory is to do with the keytool utility that comes with the Java jdk. - - $ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950 - Enter keystore password: secret - Re-enter new password: secret - What is your first and last name? - [Unknown]: localhost - What is the name of your organizational unit? - [Unknown]: Keycloak - What is the name of your organization? - [Unknown]: Red Hat - What is the name of your City or Locality? - [Unknown]: Westford - What is the name of your State or Province? - [Unknown]: MA - What is the two-letter country code for this unit? - [Unknown]: US - Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct? - [no]: yes - + +$ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950 + Enter keystore password: secret + Re-enter new password: secret + What is your first and last name? + [Unknown]: localhost + What is the name of your organizational unit? + [Unknown]: Keycloak + What is the name of your organization? + [Unknown]: Red Hat + What is the name of your City or Locality? + [Unknown]: Westford + What is the name of your State or Province? + [Unknown]: MA + What is the two-letter country code for this unit? + [Unknown]: US + Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct? + [no]: yes + You should answer What is your first and last name ? question with @@ -693,44 +697,44 @@ All configuration options are optional. Default value for directory is The first thing to do is generate a Certificate Request: - - $ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq - + +$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq + Where yourdomain is a DNS name for which this certificate is generated for. Keytool generates the request: - - -----BEGIN NEW CERTIFICATE REQUEST----- - MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y - ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0 - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY - Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1 - 29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as - H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw - Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35 - 2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i - n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf - PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ - 9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X - MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S - vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8= - -----END NEW CERTIFICATE REQUEST----- - + +-----BEGIN NEW CERTIFICATE REQUEST----- +MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y +ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY +Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1 +29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as +H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw +Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35 +2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i +n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf +PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ +9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X +MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S +vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8= +-----END NEW CERTIFICATE REQUEST----- + Send this ca request to your CA. The CA will issue you a signed certificate and send it to you. Before you import your new cert, you must obtain and import the root certificate of the CA. You can download the cert from CA (ie.: root.crt) and import as follows: - - $ keytool -import -keystore keycloak.jks -file root.crt -alias root - + +$ keytool -import -keystore keycloak.jks -file root.crt -alias root + Last step is import your new CA generated certificate to your keystore: - - $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer - + +$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer +
@@ -744,18 +748,19 @@ All configuration options are optional. Default value for directory is
To the security-realms element add: - - - - - - - ]]> + + + + + + + +]]> Find the element <server name="default-server"> (it's a child element of <subsystem xmlns="urn:jboss:domain:undertow:1.0">) and add: - - ]]> + ]]> Check the Wildfly Undertow documentation for more information on fine tuning the socket connections. @@ -813,44 +818,20 @@ All configuration options are optional. Default value for directory is
- Adding Keycloak server in Domain Mode + Keycloak server in Domain Mode In domain mode, you start the server with the "domain" command instead of the "standalone" command. In this case, the Keycloak subsystem is defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml. Inside domain.xml, you will see more than one - profile. A Keycloak subsystem can be defined in zero or more of those profiles. + profile. The Keycloak subsystem is defined for all initial profiles. - To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the extensions - element add the Keycloak extension: - - ... - - -]]> - Then you need to add the server to the required server profiles. By default WildFly starts two servers - in the main-server-group which uses the full profile. To add Keycloak for this profile add the Keycloak - subsystem to the profile element with name full: - - ... - - - true - auth - - -]]> + THe server is also added to server profiles. By default two servers are started + in the main-server-group which uses the full profile. - To configure the server copy standalone/configuration/keycloak-server.json to - domain/servers/<SERVER NAME>/configuration. The configuration should be identical + You need to make sure domain/servers/<SERVER NAME>/configuration is identical for all servers in a group. - - Follow the Clustering section of the documentation to configure Keycloak - for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode. - To deploy custom providers and themes you should deploys these as modules and make sure the modules are available to all servers in the group. See Providers and @@ -865,12 +846,12 @@ All configuration options are optional. Default value for directory is To do this, add the default-web-module attribute in the Undertow subystem in standalone.xml. - - - - - + + + + ]]> diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java index d7ca253778..b0cbc6a3e1 100755 --- a/events/api/src/main/java/org/keycloak/events/Errors.java +++ b/events/api/src/main/java/org/keycloak/events/Errors.java @@ -44,8 +44,7 @@ public interface Errors { String NOT_ALLOWED = "not_allowed"; - String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists"; - String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists"; + String FEDERATED_IDENTITY_EXISTS = "federated_identity_account_exists"; String SSL_REQUIRED = "ssl_required"; String USER_SESSION_NOT_FOUND = "user_session_not_found"; @@ -53,4 +52,5 @@ public interface Errors { String EMAIL_SEND_FAILED = "email_send_failed"; String INVALID_EMAIL = "invalid_email"; String IDENTITY_PROVIDER_LOGIN_FAILURE = "identity_provider_login_failure"; + String IDENTITY_PROVIDER_ERROR = "identity_provider_error"; } diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java index 0a8b3aecee..b75728bd15 100755 --- a/events/api/src/main/java/org/keycloak/events/EventType.java +++ b/events/api/src/main/java/org/keycloak/events/EventType.java @@ -48,6 +48,8 @@ public enum EventType { SEND_VERIFY_EMAIL_ERROR(true), SEND_RESET_PASSWORD(true), SEND_RESET_PASSWORD_ERROR(true), + SEND_IDENTITY_PROVIDER_LINK(true), + SEND_IDENTITY_PROVIDER_LINK_ERROR(true), RESET_PASSWORD(true), RESET_PASSWORD_ERROR(true), @@ -60,12 +62,12 @@ public enum EventType { IDENTITY_PROVIDER_LOGIN(false), IDENTITY_PROVIDER_LOGIN_ERROR(false), + IDENTITY_PROVIDER_FIRST_LOGIN(true), + IDENTITY_PROVIDER_FIRST_LOGIN_ERROR(true), IDENTITY_PROVIDER_RESPONSE(false), IDENTITY_PROVIDER_RESPONSE_ERROR(false), IDENTITY_PROVIDER_RETRIEVE_TOKEN(false), IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR(false), - IDENTITY_PROVIDER_ACCCOUNT_LINKING(false), - IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false), IMPERSONATE(true), CUSTOM_REQUIRED_ACTION(true), CUSTOM_REQUIRED_ACTION_ERROR(true), diff --git a/examples/js-console/src/main/webapp/WEB-INF/web.xml b/examples/js-console/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..a7460c2e9f --- /dev/null +++ b/examples/js-console/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,8 @@ + + + + js-console + \ No newline at end of file diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java index f6228959c8..d6475aaa61 100755 --- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java +++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java @@ -1,6 +1,7 @@ package org.keycloak.examples.federation.properties; import org.keycloak.models.CredentialValidationOutput; +import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; @@ -106,6 +107,12 @@ public abstract class BasePropertiesFederationProvider implements UserFederation } + @Override + public void preRemove(RealmModel realm, GroupModel group) { + // complete we dont'care if a role is removed + + } + /** * See if the user is still in the properties file * diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java index 164cf79cdb..5467a425f0 100755 --- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java +++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java @@ -63,4 +63,6 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat throw new IllegalStateException("Remove not supported"); } + + } diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 30eb0558fe..12b358e2db 100755 --- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -7,6 +7,7 @@ import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; import org.keycloak.models.ClientModel; +import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; @@ -15,6 +16,7 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -294,6 +296,12 @@ public class ExportUtils { } } + List groups = new LinkedList<>(); + for (GroupModel group : user.getGroups()) { + groups.add(ModelToRepresentation.buildGroupPath(group)); + } + userRep.setGroups(groups); + return userRep; } diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java old mode 100644 new mode 100755 index ec1a905010..65831f250c --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java @@ -12,6 +12,7 @@ import org.jboss.logging.Logger; import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator; import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator; import org.keycloak.models.CredentialValidationOutput; +import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; @@ -105,12 +106,17 @@ public class KerberosFederationProvider implements UserFederationProvider { } + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + @Override public boolean isValid(RealmModel realm, UserModel local) { // KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm(); - return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL)); + return kerberosPrincipal.equalsIgnoreCase(local.getFirstAttribute(KERBEROS_PRINCIPAL)); } @Override diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java index ea3f953c5a..f38eeb5be1 100644 --- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java +++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java @@ -17,6 +17,7 @@ import javax.security.auth.login.LoginException; import org.jboss.logging.Logger; import org.keycloak.federation.kerberos.CommonKerberosConfig; +import org.keycloak.models.ModelException; /** * @author Marek Posolda @@ -54,6 +55,8 @@ public class KerberosUsernamePasswordAuthenticator { String message = le.getMessage(); logger.debug("Message from kerberos: " + message); + checkKerberosServerAvailable(le); + // Bit cumbersome, but seems to work with tested kerberos servers boolean exists = (!message.contains("Client not found")); return exists; @@ -74,11 +77,19 @@ public class KerberosUsernamePasswordAuthenticator { logoutSubject(); return true; } catch (LoginException le) { + checkKerberosServerAvailable(le); + logger.debug("Failed to authenticate user " + username, le); return false; } } + protected void checkKerberosServerAvailable(LoginException le) { + if (le.getMessage().contains("Port Unreachable")) { + throw new ModelException("Kerberos unreachable", le); + } + } + /** * Returns true if user was successfully authenticated against Kerberos diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index 322155b61a..3704f7626f 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -12,6 +12,7 @@ import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore; import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig; import org.keycloak.federation.ldap.mappers.LDAPFederationMapper; import org.keycloak.models.CredentialValidationOutput; +import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; @@ -319,6 +320,11 @@ public class LDAPFederationProvider implements UserFederationProvider { // TODO: Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper? } + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + public boolean validPassword(RealmModel realm, UserModel user, String password) { if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) { // Use Kerberos JAAS (Krb5LoginModule) diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java index 020d349150..0a253d1fb9 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java @@ -153,6 +153,10 @@ public class ExtendingThemeManager implements ThemeProvider { private List themes; + private Properties properties; + + private ConcurrentHashMap> messages = new ConcurrentHashMap<>(); + public ExtendingTheme(List themes) { this.themes = themes; } @@ -229,28 +233,41 @@ public class ExtendingThemeManager implements ThemeProvider { @Override public Properties getMessages(String baseBundlename, Locale locale) throws IOException { - Properties messages = new Properties(); - ListIterator itr = themes.listIterator(themes.size()); - while (itr.hasPrevious()) { - Properties m = itr.previous().getMessages(baseBundlename, locale); - if (m != null) { - messages.putAll(m); + if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) { + Properties messages = new Properties(); + ListIterator itr = themes.listIterator(themes.size()); + while (itr.hasPrevious()) { + Properties m = itr.previous().getMessages(baseBundlename, locale); + if (m != null) { + messages.putAll(m); + } } + + this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap()); + this.messages.get(baseBundlename).putIfAbsent(locale, messages); + + return messages; + } else { + return messages.get(baseBundlename).get(locale); } - return messages; } @Override public Properties getProperties() throws IOException { - Properties properties = new Properties(); - ListIterator itr = themes.listIterator(themes.size()); - while (itr.hasPrevious()) { - Properties p = itr.previous().getProperties(); - if (p != null) { - properties.putAll(p); + if (properties == null) { + Properties properties = new Properties(); + ListIterator itr = themes.listIterator(themes.size()); + while (itr.hasPrevious()) { + Properties p = itr.previous().getProperties(); + if (p != null) { + properties.putAll(p); + } } + this.properties = properties; + return properties; + } else { + return properties; } - return properties; } } diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties index 97f2d66fee..33d080dbb1 100644 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties @@ -126,4 +126,4 @@ locale_de=Deutsch locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (Brasil) -locale_fr=Français \ No newline at end of file +locale_fr=Fran\u00e7ais \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties index f85ebffff5..49801041cc 100755 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -133,6 +133,7 @@ federatedIdentityLinkNotActiveMessage=This identity is not active anymore. federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password. identityProviderRedirectErrorMessage=Failed to redirect to identity provider. identityProviderRemovedMessage=Identity provider removed successfully. +identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. accountDisabledMessage=Account is disabled, contact admin. @@ -150,4 +151,4 @@ locale_de=German locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (Brasil) -locale_fr=Français \ No newline at end of file +locale_fr=Fran\u00e7ais \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties new file mode 100644 index 0000000000..40743f8b60 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_es.properties @@ -0,0 +1,154 @@ +doSave=Guardar +doCancel=Cancelar +doLogOutAllSessions=Desconectar de todas las sesiones +doRemove=Eliminar +doAdd=A\u00F1adir +doSignOut=Desconectar + +editAccountHtmlTtile=Editar cuenta +federatedIdentitiesHtmlTitle=Identidades federadas +accountLogHtmlTitle=Registro de la cuenta +changePasswordHtmlTitle=Cambiar contrase\u00F1a +sessionsHtmlTitle=Sesiones +accountManagementTitle=Gesti\u00F3n de Cuenta Keycloak +authenticatorTitle=Autenticador +applicationsHtmlTitle=Aplicaciones + +authenticatorCode=C\u00F3digo de un solo uso +email=Email +firstName=Nombre +givenName=Nombre de pila +fullName=Nombre completo +lastName=Apellidos +familyName=Apellido +password=Contrase\u00F1a +passwordConfirm=Confirma la contrase\u00F1a +passwordNew=Nueva contrase\u00F1a +username=Usuario +address=Direcci\u00F3n +street=Calle +locality=Ciudad o Municipio +region=Estado, Provincia, o Regi\u00F3n +postal_code=C\u00F3digo Postal +country=Pa\u00EDs +emailVerified=Email verificado +gssDelegationCredential=GSS Delegation Credential + +role_admin=Administrador +role_realm-admin=Administrador del dominio +role_create-realm=Crear dominio +role_view-realm=Ver dominio +role_view-users=Ver usuarios +role_view-applications=Ver aplicaciones +role_view-clients=Ver clientes +role_view-events=Ver eventos +role_view-identity-providers=Ver proveedores de identidad +role_manage-realm=Gestionar dominio +role_manage-users=Gestionar usuarios +role_manage-applications=Gestionar aplicaciones +role_manage-identity-providers=Gestionar proveedores de identidad +role_manage-clients=Gestionar clientes +role_manage-events=Gestionar eventos +role_view-profile=Ver perfil +role_manage-account=Gestionar cuenta +role_read-token=Leer token +role_offline-access=Acceso sin conexi\u00F3n +client_account=Cuenta +client_security-admin-console=Consola de Administraci\u00F3n de Seguridad +client_realm-management=Gesti\u00F3n de dominio +client_broker=Broker + + +requiredFields=Campos obligatorios +allFieldsRequired=Todos los campos obligatorios + +backToApplication=« Volver a la aplicaci\u00F3n +backTo=Volver a {0} + +date=Fecha +event=Evento +ip=IP +client=Cliente +clients=Clientes +details=Detalles +started=Iniciado +lastAccess=\u00DAltimo acceso +expires=Expira +applications=Aplicaciones + +account=Cuenta +federatedIdentity=Identidad federada +authenticator=Autenticador +sessions=Sesiones +log=Regisro + +application=Aplicaci\u00F3n +availablePermissions=Permisos disponibles +grantedPermissions=Permisos concedidos +grantedPersonalInfo=Informaci\u00F3n personal concedida +additionalGrants=Permisos adicionales +action=Acci\u00F3n +inResource=en +fullAccess=Acceso total +offlineToken=C\u00F3digo de autorizaci\u00F3n offline +revoke=Revocar permiso + +configureAuthenticators=Autenticadores configurados +mobile=M\u00F3vil +totpStep1=Instala FreeOTP o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en Google Play y en la App Store de Apple. +totpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave. +totpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n + +missingUsernameMessage=Por favor indica tu usuario. +missingFirstNameMessage=Por favor indica el nombre. +invalidEmailMessage=Email no v\u00E1lido +missingLastNameMessage=Por favor indica tus apellidos. +missingEmailMessage=Por favor indica el email. +missingPasswordMessage=Por favor indica tu contrase\u00F1a. +notMatchPasswordMessage=Las contrase\u00F1as no coinciden. + +missingTotpMessage=Por favor indica tu c\u00F3digo de autenticaci\u00F3n +invalidPasswordExistingMessage=La contrase\u00F1a actual no es correcta. +invalidPasswordConfirmMessage=La confirmaci\u00F3n de contrase\u00F1a no coincide. +invalidTotpMessage=El c\u00F3digo de autenticaci\u00F3n no es v\u00E1lido. + +usernameExistsMessage=El usuario ya existe +emailExistsMessage=El email ya existe + +readOnlyUserMessage=No puedes actualizar tu usuario porque tu cuenta es de solo lectura. +readOnlyPasswordMessage=No puedes actualizar tu contrase\u00F1a porque tu cuenta es de solo lectura. + +successTotpMessage=Aplicaci\u00F3n de autenticaci\u00F3n m\u00F3vil configurada. +successTotpRemovedMessage=Aplicaci\u00F3n de autenticaci\u00F3n m\u00F3vil eliminada. + +successGrantRevokedMessage=Permiso revocado correctamente + +accountUpdatedMessage=Tu cuenta se ha actualizado. +accountPasswordUpdatedMessage=Tu contrase\u00F1a se ha actualizado. + +missingIdentityProviderMessage=Proveedor de identidad no indicado. +invalidFederatedIdentityActionMessage=Acci\u00F3n no v\u00E1lida o no indicada. +identityProviderNotFoundMessage=No se encontr\u00F3 un proveedor de identidad. +federatedIdentityLinkNotActiveMessage=Esta identidad ya no est\u00E1 activa +federatedIdentityRemovingLastProviderMessage=No puedes eliminar la \u00FAltima identidad federada porque no tienes fijada una contrase\u00F1a. +identityProviderRedirectErrorMessage=Error en la redirecci\u00F3n al proveedor de identidad +identityProviderRemovedMessage=Proveedor de identidad borrado correctamente. + +accountDisabledMessage=La cuenta est\u00E1 desactivada, contacta con el administrador. + +accountTemporarilyDisabledMessage=La cuenta est\u00E1 temporalmente desactivada, contacta con el administrador o int\u00E9ntalo de nuevo m\u00E1s tarde. +invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}. +invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas. +invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres num\u00E9ricos. +invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas. +invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales. +invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario. +invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular. +invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as. + +locale_de=German +locale_en=English +locale_it=Italian +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_fr=Fran\u00E7ais +locale_es=Espa\u00F1ol diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties index 6831687235..56e41d4b94 100644 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_fr.properties @@ -10,7 +10,7 @@ doSignOut=D\u00e9connexion editAccountHtmlTtile=Edition du compte federatedIdentitiesHtmlTitle=Identit\u00e9s f\u00e9d\u00e9r\u00e9es -accountLogHtmlTitle=Acces au compte +accountLogHtmlTitle=Acc\u00e8s au compte changePasswordHtmlTitle=Changer de mot de passe sessionsHtmlTitle=Sessions accountManagementTitle=Gestion de Compte Keycloak @@ -22,7 +22,7 @@ email=Courriel firstName=Nom givenName=Pr\u00e9nom fullName=Nom Complet -lastName=Last name +lastName=Nom familyName=Nom de Famille password=Mot de passe passwordConfirm=Confirmation @@ -31,7 +31,7 @@ username=Compte address=Adresse street=Rue locality=Ville ou Localit\u00e9 -region=State, Province, or R\u00e9gion +region=\u00c9tat, Province ou R\u00e9gion postal_code=Code Postal country=Pays emailVerified=Courriel v\u00e9rifi\u00e9 @@ -50,7 +50,7 @@ role_manage-realm=G\u00e9rer le domaine role_manage-users=G\u00e9rer les utilisateurs role_manage-applications=G\u00e9rer les applications role_manage-identity-providers=G\u00e9rer les fournisseurs d''identit\u00e9s -role_manage-clients=G\u00e9rer les clients +role_manage-clients=G\u00e9rer les clients role_manage-events=G\u00e9rer les \u00e9v\u00e9nements role_view-profile=Voir le profile role_manage-account=G\u00e9rer le compte @@ -63,7 +63,7 @@ client_broker=Broker requiredFields=Champs obligatoires -allFieldsRequired=Tous les champs obligatoires +allFieldsRequired=Tous les champs sont obligatoires backToApplication=« Revenir \u00e0 l''application backTo=Revenir \u00e0 {0} @@ -74,9 +74,9 @@ ip=IP client=Client clients=Clients details=D\u00e9tails -started=S\u00e9lectionn\u00e9 +started=D\u00e9but lastAccess=Dernier acc\u00e8s -expires=Expires +expires=Expiration applications=Applications account=Compte @@ -88,7 +88,7 @@ log=Connexion application=Application availablePermissions=Permissions Disponibles grantedPermissions=Permissions accord\u00e9es -grantedPersonalInfo=Informations personnels accord\u00e9es +grantedPersonalInfo=Informations personnelles accord\u00e9es additionalGrants=Droits additionnels action=Action inResource=dans @@ -99,7 +99,7 @@ revoke=R\u00e9voquer un droit configureAuthenticators=Authentifications configur\u00e9es. mobile=T\u00e9l\u00e9phone mobile totpStep1=Installez FreeOTP ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur Google Play et Apple App Store. -totpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef. +totpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9. totpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer. missingUsernameMessage=Veuillez entrer votre nom d''utilisateur. @@ -140,17 +140,18 @@ identityProviderRemovedMessage=Le fournisseur d''identit\u00e9 a \u00e9t\u00e9 s accountDisabledMessage=Ce compte est d\u00e9sactiv\u00e9, veuillez contacter votre administrateur. accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, veuillez contacter votre administrateur ou r\u00e9essayez plus tard.. -invalidPasswordMinLengthMessage=Mot de passe invalide : longueur minimale {0}. -invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule. -invalidPasswordMinDigitsMessage=Mot de passe invalide : doit contenir au moins {0} chiffre(s). -invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule. -invalidPasswordMinSpecialCharsMessage=Mot de passe invalide : doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux. -invalidPasswordNotUsernameMessage=Mot de passe invalide : ne doit pas etre identique au nom d''utilisateur. -invalidPasswordRegexPatternMessage=Mot de passe invalide : ne valide pas l''expression rationnelle. -invalidPasswordHistoryMessage=Mot de passe invalide : ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe. +invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}. +invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule. +invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s). +invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule. +invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux. +invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas \u00eatre identique au nom d''utilisateur. +invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l''expression rationnelle. +invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe. locale_de=German locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (Brasil) locale_fr=Fran\u00e7ais +locale_es=Espa\u00F1ol diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties index da7e9d01ab..2aa902a8be 100755 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_it.properties @@ -125,4 +125,4 @@ locale_de=German locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (Brasil) -locale_fr=Français \ No newline at end of file +locale_fr=Fran\u00e7ais \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties index a569cfcd0c..37c0106632 100644 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties @@ -147,4 +147,4 @@ locale_de=Deutsch locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (BR) -locale_fr=Français \ No newline at end of file +locale_fr=Fran\u00e7ais \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/index.ftl b/forms/common-themes/src/main/resources/theme/base/admin/index.ftl index 1584d0d93a..0cdadfb0cb 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/index.ftl +++ b/forms/common-themes/src/main/resources/theme/base/admin/index.ftl @@ -25,6 +25,7 @@ + @@ -37,6 +38,7 @@ + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties index 87640e0796..295a8cecf3 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties @@ -22,7 +22,7 @@ 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 Forget password +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. diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index f13d6e8e65..e274ca71d0 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -22,7 +22,7 @@ registrationEmailAsUsername=Email as username registrationEmailAsUsername.tooltip=If enabled then username field is hidden from registration form and email is used as username for new user. editUsernameAllowed=Edit username editUsernameAllowed.tooltip=If enabled, the username field is editable, readonly otherwise. -resetPasswordAllowed=Forget password +resetPasswordAllowed=Forgot password resetPasswordAllowed.tooltip=Show a link on login page for user to click on when they have forgotten their credentials. rememberMe=Remember Me rememberMe.tooltip=Show checkbox on login page to allow user to remain logged in between browser restarts until session expires. @@ -109,6 +109,7 @@ realm-tab-email=Email realm-tab-themes=Themes realm-tab-cache=Cache realm-tab-tokens=Tokens +realm-tab-client-initial-access=Initial Access Tokens realm-tab-security-defenses=Security Defenses realm-tab-general=General add-realm=Add Realm @@ -271,6 +272,9 @@ import-client-certificate=Import Client Certificate jwt-import.key-alias.tooltip=Archive alias for your certificate. secret=Secret regenerate-secret=Regenerate Secret +registrationAccessToken=Registration access token +registrationAccessToken.regenerate=Regenerate registration access token +registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service. add-role=Add Role role-name=Role Name composite=Composite @@ -374,6 +378,7 @@ table-of-identity-providers=Table of identity providers add-provider.placeholder=Add provider... provider=Provider gui-order=GUI order +first-broker-login-flow=First Login Flow redirect-uri=Redirect URI redirect-uri.tooltip=The redirect uri to use when configuring the identity provider. alias=Alias @@ -393,6 +398,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t trust-email=Trust Email trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm. gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page). +first-broker-login-flow.tooltip=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. openid-connect-config=OpenID Connect Config openid-connect-config.tooltip=OIDC SP and external IDP configuration. authorization-url=Authorization URL @@ -465,3 +471,17 @@ identity-provider-mappers=Identity Provider Mappers create-identity-provider-mapper=Create Identity Provider Mapper add-identity-provider-mapper=Add Identity Provider Mapper client.description.tooltip=Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description} + +expires=Expires +expiration=Expiration +count=Count +remainingCount=Remaining count +created=Created +back=Back +initial-access-tokens=Initial Access Tokens +add-initial-access-tokens=Add Initial Access Token +initial-access-token=Initial Access Token +initial-access.copyPaste.tooltip=Copy/paste the initial access token before navigating away from this page as it's not posible to retrieve later +continue=Continue +initial-access-token.confirm.title=Copy Initial Access Token +initial-access-token.confirm.text=Please copy and paste the initial access token before confirming as it can't be retrieved later diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties new file mode 100644 index 0000000000..170720a70b --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties @@ -0,0 +1,466 @@ +# Common messages +enabled=Habilitado +name=Nombre +save=Guardar +cancel=Cancelar +onText=SI +offText=NO +client=Cliente +clients=Clientes +clear=Limpiar +selectOne=Selecciona uno... + +true=S\u00ED +false=No + + +# Realm settings +realm-detail.enabled.tooltip=Los usuarios y clientes solo pueden acceder a un dominio si est\u00E1 habilitado +registrationAllowed=Registro de usuario +registrationAllowed.tooltip=Habilitar/deshabilitar la p\u00E1gina de registro. Un enlace para el registro se motrar\u00E1 tambi\u00E9n en la p\u00E1gina de inicio de sesi\u00F3n. +registrationEmailAsUsername=Email como nombre de usuario +registrationEmailAsUsername.tooltip=Si est\u00E1 habilitado el nombre de usuario queda oculto del formulario de registro y el email se usa como nombre de usuario para los nuevos usuarios. +editUsernameAllowed=Editar nombre de usuario +editUsernameAllowed.tooltip=Si est\u00E1 habilitado, el nombre de usuario es editable, en otro caso es de solo lectura. +resetPasswordAllowed=Olvido contrase\u00F1a +resetPasswordAllowed.tooltip=Muestra un enlace en la p\u00E1gina de inicio de sesi\u00F3n para que el usuario haga clic cuando ha olvidado sus credenciales. +rememberMe=Seguir conectado +rememberMe.tooltip=Muestra la casilla de selecci\u00F3n en la p\u00E1gina de inicio de sesi\u00F3n para permitir al usuario permanecer conectado entre reinicios del navegador hasta que la sesi\u00F3n expire. +verifyEmail=Verificar email +verifyEmail.tooltip=Forzar al usuario a verificar su direcci\u00F3n de email la primera vez que inicie sesi\u00F3n. +sslRequired=Solicitar SSL +sslRequired.option.all=todas las peticiones +sslRequired.option.external=peticiones externas +sslRequired.option.none=ninguna +sslRequired.tooltip=\u00BFEs HTTP obligatorio? 'ninguna' significa que HTTPS no es obligatorio para ninguna direcic\u00F3n IP de cliente, 'peticiones externas' indica que localhost y las direcciones IP privadas pueden acceder sin HTTPS, 'todas las peticiones' significa que HTTPS es obligatorio para todas las direcciones IP. +publicKey=Clave p\u00FAblica +gen-new-keys=Generar nuevas claves +certificate=Certificado +host=Host +smtp-host=Host SMTP +port=Puerto +smtp-port=Puerto SMTP (por defecto 25) +from=De +sender-email-addr=Email del emisor +enable-ssl=Habilitar SSL +enable-start-tls=Habilitar StartTLS +enable-auth=Habilitar autenticaci\u00F3n +username=Usuario +login-username=Usuario +password=Contrase\u00F1a +login-password=Contrase\u00F1a +login-theme=Tema de inicio de sesi\u00F3n +select-one=Selecciona uno... +login-theme.tooltip=Selecciona el tema para las p\u00E1gina de inicio de sesi\u00F3n, TOTP, permisos, registro y recordatorio de contrase\u00F1a. +account-theme=Tema de cuenta +account-theme.tooltip=Selecciona el tema para las p\u00E1ginas de gesti\u00F3n de la cuenta de usuario. +admin-console-theme=Tema de consola de administraci\u00F3n +select-theme-admin-console=Selecciona el tema para la consola de administraci\u00F3n. +email-theme=Tema de email +select-theme-email=Selecciona el tema para los emails que son enviados por el servidor. +i18n-enabled=Internacionalizaci\u00F3n activa +supported-locales=Idiomas soportados +supported-locales.placeholder=Indica el idioma y pulsa Intro +default-locale=Idioma por defecto +realm-cache-enabled=Cach\u00E9 de dominio habilitada +realm-cache-enabled.tooltip=Activar/desactivar la cach\u00E9 para el dominio, cliente y datos de roles. +user-cache-enabled=Cach\u00E9 de usuario habilitada +user-cache-enabled.tooltip=Habilitar/deshabilitar la cach\u00E9 de usuarios y de asignaciones de usuarios a roles. +revoke-refresh-token=Revocar el token de actualizaci\u00F3n +revoke-refresh-token.tooltip=Si est\u00E1 activado los tokens de actualizaci\u00F3n solo pueden usarse una vez. En otro caso los tokens de actualizaci\u00F3n no se revocan cuando se utilizan y pueden ser usado m\u00FAltiples veces. +sso-session-idle=Sesiones SSO inactivas +seconds=Segundos +minutes=Minutos +hours=Horas +days=D\u00EDas +sso-session-max=Tiempo m\u00E1ximo sesi\u00F3n SSO +sso-session-idle.tooltip=Tiempo m\u00E1ximo que una sesi\u00F3n puede estar inactiva antes de que expire. Los tokens y sesiones de navegador son invalidadas cuando la sesi\u00F3n expira. +sso-session-max.tooltip=Tiempo m\u00E1ximo antes de que una sesi\u00F3n expire. Los tokesn y sesiones de navegador son invalidados cuando una sesi\u00F3n expira. +offline-session-idle=Inactividad de sesi\u00F3n sin conexi\u00F3n +offline-session-idle.tooltip=Tiempo m\u00E1ximo inactivo de una sesi\u00F3n sin conexi\u00F3n antes de que expire. Necesitas usar un token sin conexi\u00F3n para refrescar al menos una vez dentro de este periodo, en otro caso la sesi\u00F3n sin conexi\u00F3n expirar\u00E1. +access-token-lifespan=Duraci\u00F3n del token de acceso +access-token-lifespan.tooltip=Tiempo m\u00E1ximo antes de que un token de acceso expire. Se recomiena que esta valor sea corto en relaci\u00F3n al tiempo m\u00E1ximo de SSO +client-login-timeout=Tiempo m\u00E1ximo de autenticaci\u00F3n +client-login-timeout.tooltip=Tiempo m\u00E1ximo que un cliente tien para finalizar el protocolo de obtenci\u00F3n del token de acceso. Deber\u00EDa ser normalmente del orden de 1 minuto. +login-timeout=Tiempo m\u00E1ximo de desconexi\u00F3n +login-timeout.tooltip=Tiempo m\u00E1xmo que un usuario tiene para completar el inicio de sesi\u00F3n. Se recomienda que sea relativamente alto. 30 minutos o m\u00E1s. +login-action-timeout=Tiempo m\u00E1ximo de acci\u00F3n en el inicio de sesi\u00F3n +login-action-timeout.tooltip=Tiempo m\u00E1ximo que un usuario tiene para completar acciones relacionadas con el inicio de sesi\u00F3n, como la actualizaci\u00F3n de contrase\u00F1a o configuraci\u00F3n de TOTP. Es recomendado que sea relativamente alto. 5 minutos o m\u00E1s. +headers=Cabeceras +brute-force-detection=Detecci\u00F3n de ataques por fuerza bruta +x-frame-options=X-Frame-Options +click-label-for-info=Haz clic en el enlace de la etiqueta para obtener m\u00E1s informaci\u00F3n. El valor por defecto evita que las p\u00E1ginas sean incluidaos desde iframes externos. +content-sec-policy=Content-Security-Policy +max-login-failures=N\u00FAmero m\u00E1ximo de fallos de inicios de sesi\u00F3n +max-login-failures.tooltip=Indica cuantos fallos se permiten antes de que se dispare una espera. +wait-increment=Incremento de espera +wait-increment.tooltip=Cuando se ha alcanzado el umbral de fallo, \u00BFcuanto tiempo debe estar un usuario bloqueado? +quick-login-check-millis=Tiempo en milisegundos entre inicios de sesi\u00F3n r\u00E1pidos +quick-login-check-millis.tooltip=Si ocurren errores de forma concurrente y muy r\u00E1pida, bloquear al usuario. +min-quick-login-wait=Tiempo m\u00EDnimo entre fallos de conexi\u00F3n r\u00E1pidos +min-quick-login-wait.tooltip=Cuanto tiempo se debe esperar tras un fallo en un intento r\u00E1pido de identificaci\u00F3n +max-wait=Espera m\u00E1xima +max-wait.tooltip=Tiempo m\u00E1ximo que un usuario quedar\u00E1 bloqueado. +failure-reset-time=Reinicio del contador de errores +failure-reset-time.tooltip=\u00BFCuando se debe reiniciar el contador de errores? +realm-tab-login=Inicio de sesi\u00F3n +realm-tab-keys=Claves +realm-tab-email=Email +realm-tab-themes=Temas +realm-tab-cache=Cache +realm-tab-tokens=Tokens +realm-tab-security-defenses=Defensas de seguridad +realm-tab-general=General +add-realm=A\u00F1adir dominio + +#Session settings +realm-sessions=Sesiones de dominio +revocation=Revocaci\u00F3n +logout-all=Desconectar todo +active-sessions=Sesiones activas +sessions=Sesiones +not-before=No antes de +not-before.tooltip=Revocar cualquier token emitido antes de esta fecha. +set-to-now=Fijar a ahora +push=Push +push.tooltip=Para cada cliente que tiene una URL de administraci\u00F3n, notificarlos the las nuevas pol\u00EDticas de revocaci\u00F3n. + +#Protocol Mapper +usermodel.prop.label=Propiedad +usermodel.prop.tooltip=Nombre del m\u00E9todo de propiedad in la interfaz UserModel. Por ejemplo, un valor de 'email' referenciar\u00EDa al m\u00E9todo UserModel.getEmail(). +usermodel.attr.label=Atributo de usuario +usermodel.attr.tooltip=Nombre del atributo de usuario almacenado que es el nombre del atributo dentro del map UserModel.attribute. +userSession.modelNote.label=Nota sesi\u00F3n usuario +userSession.modelNote.tooltip=Nombre de la nota almacenada en la sesi\u00F3n de usuario dentro del mapa UserSessionModel.note +multivalued.label=Valores m\u00FAltiples +multivalued.tooltip=Indica si el atributo soporta m\u00FAltiples valores. Si est\u00E1 habilitado, la lista de todos los valores de este atributo se fijar\u00E1 como reclamaci\u00F3n. Si est\u00E1 deshabilitado, solo el primer valor ser\u00E1 fijado como reclamaci\u00F3n. +selectRole.label=Selecciona rol +selectRole.tooltip=Introduce el rol en la caja de texto de la izquierda, o haz clic en este bot\u00F3n para navegar y buscar el rol que quieres. +tokenClaimName.label=Nombre de reclamo del token +tokenClaimName.tooltip=Nombre del reclamo a insertar en el token. Puede ser un nombre completo como 'address.street'. En este caso, se crear\u00E1 un objeto JSON anidado. +jsonType.label=Tipo JSON de reclamaci\u00F3n +jsonType.tooltip=El tipo de JSON que deber\u00EDa ser usado para rellenar la petici\u00F3n de JSON en el token. long, int, boolean y String son valores v\u00E1lidos +includeInIdToken.label=A\u00F1adir al token de ID +includeInAccessToken.label=A\u00F1adir al token de acceso +includeInAccessToken.tooltip=\u00BFDeber\u00EDa a\u00F1adirse la identidad reclamada al token de acceso? + + +# client details +clients.tooltip=Los clientes son aplicaciones de navegador de confianza y servicios web de un dominio. Estos clientes pueden solicitar un inicio de sesi\u00F3n. Tambi\u00E9n puedes definir roles espec\u00EDficos de cliente. +search.placeholder=Buscar... +create=Crear +import=Importar +client-id=ID Cliente +base-url=URL Base +actions=Acciones +not-defined=No definido +edit=Editar +delete=Borrar +no-results=Sin resultados +no-clients-available=No hay clientes disponibles +add-client=A\u00F1adir Cliente +select-file=Selecciona archivo +view-details=Ver detalles +clear-import=Limpiar importaci\u00F3n +client-id.tooltip=Indica el identificador (ID) referenciado en URIs y tokens. Por ejemplo 'my-client' +client.name.tooltip=Indica el nombre visible del cliente. Por ejemplo 'My Client'. Tambi\u00E9n soporta claves para vallores localizados. Por ejemplo: ${my_client} +client.enabled.tooltip=Los clientes deshabilitados no pueden iniciar una identificaci\u00F3n u obtener c\u00F3digos de acceso. +consent-required=Consentimiento necesario +consent-required.tooltip=Si est\u00E1 habilitado, los usuarios tienen que consentir el acceso del cliente. +direct-grants-only=Solo permisos directos +direct-grants-only.tooltip=Cuando est\u00E1 habilitado, el cliente solo puede obtener permisos de la API REST. +client-protocol=Protocolo del Cliente +client-protocol.tooltip='OpenID connect' permite a los clientes verificar la identidad del usuario final basado en la autenticaci\u00F3n realizada por un servidor de autorizaci\u00F3n. 'SAML' habilita la autenticaci\u00F3n y autorizaci\u00F3n de escenarios basados en web incluyendo cross-domain y single sign-on (SSO) y utiliza tokdne de seguridad que contienen afirmaciones para pasar informaci\u00F3n. +access-type=Tipo de acceso +access-type.tooltip=Los clientes 'Confidential' necesitan un secreto para iniciar el protocolo de identificaci\u00F3n. Los clientes 'Public' no requieren un secreto. Los clientes 'Bearer-only' son servicios web que nunca inician un login. +service-accounts-enabled=Cuentas de servicio habilitadas +service-accounts-enabled.tooltip=Permitir autenticar este cliente contra Keycloak y recivir un token de acceso dedicado para este cliente. +include-authnstatement=Incluir AuthnStatement +include-authnstatement.tooltip=null +sign-documents=Firmar documentos +sign-documents.tooltip=\u00BFDeber\u00EDa el dominio firmar los documentos SAML? +sign-assertions=Firmar aserciones +sign-assertions.tooltip=\u00BFDeber\u00EDan firmarse las aserciones en documentos SAML? Este ajuste no es necesario si el documento ya est\u00E1 siendo firmado. +signature-algorithm=Algoritmo de firma +signature-algorithm.tooltip=El algoritmo de firma usado para firmar los documentos. +canonicalization-method=M\u00E9todo de canonicalizaci\u00F3n +canonicalization-method.tooltip=M\u00E9todo de canonicalizaci\u00F3n para las firmas XML +encrypt-assertions=Cifrar afirmaciones +encrypt-assertions.tooltip=\u00BFDeber\u00EDan cifrarse las afirmaciones SAML con la clave p\u00FAblica del cliente usando AES? +client-signature-required=Firma de Cliente requerida +client-signature-required.tooltip=\u00BFFirmar\u00E1 el cliente sus peticiones y respuestas SAML? \u00BFY deber\u00EDan ser validadas? +force-post-binding=Forzar enlaces POST +force-post-binding.tooltip=Usar siempre POST para las respuestas +front-channel-logout=Desonexi\u00F3n en primer plano (Front Channel) +front-channel-logout.tooltip=Cuando est\u00E1 activado, la desconexi\u00F3n require una redirecci\u00F3n del navegador hacia el cliente. Cuando no est\u00E1 activado, el servidor realiza una invovaci\u00F3n de desconexi\u00F3n en segundo plano. +force-name-id-format=Forzar formato NameID +force-name-id-format.tooltip=Ignorar la petici\u00F3n de sujeto NameID y usar la configurada en la consola de administraci\u00F3n. +name-id-format=Formato de NameID +name-id-format.tooltip=El formato de NameID que se usar\u00E1 para el t\u00EDtulo +root-url=URL ra\u00EDz +root-url.tooltip=URL ra\u00EDz a\u00F1adida a las URLs relativas +valid-redirect-uris=URIs de redirecci\u00F3n v\u00E1lidas +valid-redirect-uris.tooltip=Patr\u00F3n de URI v\u00E1lida para la cual un navegador puede solicitar la redirecci\u00F3n tras un inicio o cierre de sesi\u00F3n completado. Se permiten comodines simples p.ej. 'http://example.com/*'. Tambi\u00E9n se pueden indicar rutas relativas p.ej. '/my/relative/path/*'. Las rutas relativas generar\u00E1n una URI de redirecci\u00F3n usando el host y puerto de la petici\u00F3n. Para SAML, se deben fijar patrones de URI v\u00E1lidos si quieres confiar en la URL del servicio del consumidor indicada en la petici\u00F3n de inicio de sesi\u00F3n. +base-url.tooltip=URL por defecto para usar cuando el servidor de autorizaci\u00F3n necesita redirigir o enviar de vuelta al cliente. +admin-url=URL de administraci\u00F3n +admin-url.tooltip=URL a la interfaz de administraci\u00F3n del cliente. Fija este valor si el cliente soporta el adaptador de REST. Esta API REST permite al servidor de autenticaci\u00F3n enviar al cliente pol\u00EDticas de revocaci\u00F3n y otras tareas administrativas. Normalment se fija a la URL base del cliente. +master-saml-processing-url=URL principal de procesamiento SAML +master-saml-processing-url.tooltip=Si est\u00E1 configurada, esta URL se usar\u00E1 para cada enlace al proveedor del servicio del consumidor de aserciones y servicios de desconexi\u00F3n \u00FAnicos. Puede ser sobreescrito de forma individual para cada enlace y servicio en el punto final de configuraci\u00F3n fina de SAML. +idp-sso-url-ref=Nombre de la URL de un SSO iniciado por el IDP +idp-sso-url-ref.tooltip=Nombre del fragmento de la URL para referenciar al cliente cuando quieres un SSO iniciado por el IDP. Dejanto esto vac\u00EDo deshabilita los SSO iniciados por el IDP. La URL referenciada desde el navegador ser\u00E1: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name} +idp-sso-relay-state=Estado de retransmisi\u00F3n de un SSO iniciado por el IDP +idp-sso-relay-state.tooltip=Estado de retransmisi\u00F3n que quiees enviar con una petici\u00F3n SAML cuando se inicia un SSO inidicado por el IDP +web-origins=Origenes web +web-origins.tooltip=Origenes CORS permitidos. Para permitir todos los or\u00EDgenes de URIs de redirecci\u00F3n v\u00E1lidas a\u00F1ade '+'. Para permitir todos los or\u00EDgenes a\u00F1ade '*'. +fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration +fine-saml-endpoint-conf.tooltip=Expande esta secci\u00F3n para configurar las URL exactas para Assertion Consumer y Single Logout Service. +assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL +assertion-consumer-post-binding-url.tooltip=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=Assertion Consumer Service Redirect Binding URL +assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL +logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n +logout-service-binding-post-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. +logout-service-redir-binding-url=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n +logout-service-redir-binding-url.tooltip=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. + +# client import +import-client=Importar Cliente +format-option=Formato +select-format=Selecciona un formato +import-file=Archivo de Importaci\u00F3n + +# client tabs +settings=Ajustes +credentials=Credenciales +saml-keys=Claves SAML +roles=Roles +mappers=Asignadores +mappers.tootip=Los asignadores de protocolos realizan transformaciones en tokens y documentos. Pueden hacer cosas como asignar datos de usuario en peticiones de protocolo, o simplemente transformar cualquier petici\u00F3n entre el cliente y el servidor de autenticaci\u00F3n. +scope=\u00C1mbito +scope.tooltip=Las asignaciones de \u00E1mbito te permiten restringir que asignaciones de roles de usuario se incluyen en el token de acceso solicitado por el cliente. +sessions.tooltip=Ver sesiones activas para este cliente. Permite ver qu\u00E9 usuarios est\u00E1n activos y cuando se identificaron. +offline-access=Acceso sin conexi\u00F3n +offline-access.tooltip=Ver sesiones sin conexi\u00F3n para este cliente. Te permite ver que usuarios han solicitado tokens sin conexi\u00F3n y cuando los solicitaron. Para revocar todos los tokens del cliente, accede a la pesta\u00F1a de Revocaci\u00F3n y fija el valor \"No antes de\" a \"now\". +clustering=Clustering +installation=Instalaci\u00F3n +installation.tooltip=Herramienta de ayuda para generar la configuraci\u00F3n de varios formatos de adaptadores de cliente que puedes descargar o copiar y pegar para configurar tus clientes. +service-account-roles=Roles de cuenta de servicio +service-account-roles.tooltip=Permitir autenticar asignaciones de rol para la cuenta de servicio dedicada a este cliente. + +# client credentials +client-authenticator=Cliente autenticador +client-authenticator.tooltip=Cliente autenticador usado para autenticar este cliente contra el servidor Keycloak +certificate.tooltip=Certificado de clinete para validar los JWT emitidos por este cliente y firmados con la clave privada del cliente de tu almac\u00E9n de claves. +no-client-certificate-configured=No se ha configurado el certificado de cliente +gen-new-keys-and-cert=Genearr nuevas claves y certificado +import-certificate=Importar Certificado +gen-client-private-key=Generar clave privada de cliente +generate-private-key=Generar clave privada +archive-format=Formato de Archivo +archive-format.tooltip=Formato de archivo Java keystore o PKCS12 +key-alias=Alias de clave +key-alias.tooltip=Alias del archivo de tu clave privada y certificado. +key-password=Contrase\u00F1a de la clave +key-password.tooltip=Contrase\u00F1a para acceder a la clave privada contenida en el archivo +store-password=Contrase\u00F1a del almac\u00E9n +store-password.tooltip=Contrase\u00F1a para acceder al archivo +generate-and-download=Generar y descargar +client-certificate-import=Importaci\u00F3n de certificado de cliente +import-client-certificate=Importar Certificado de Cliente +jwt-import.key-alias.tooltip=Alias del archivo de tu certificado. +secret=Secreto +regenerate-secret=Regenerar secreto +add-role=A\u00F1adir rol +role-name=Nombre de rol +composite=Compuesto +description=Descripci\u00F3n +no-client-roles-available=No hay roles de cliente disponibles +scope-param-required=Par\u00E1metro de \u00E1mbito obligatorio +scope-param-required.tooltip=Este rol solo ser\u00E1 concedido si el par\u00E1metro de \u00E1mbito con el nombre del rol es usado durante la petici\u00F3n de autorizaci\u00F3n/obtenci\u00F3n de token. +composite-roles=Roles compuestos +composite-roles.tooltip=Cuanto este rol es asignado/desasignado a un usuario cualquier rol asociado con \u00E9l ser\u00E1 asignado/desasignado de forma impl\u00EDcita. +realm-roles=Roles de dominio +available-roles=Roles Disponibles +add-selected=A\u00F1adir seleccionado +associated-roles=Roles Asociados +composite.associated-realm-roles.tooltip=Roles a nivel de dominio asociados con este rol compuesto. +composite.available-realm-roles.tooltip=Roles a nivel de dominio disponibles en este rol compuesto. +remove-selected=Borrar seleccionados +client-roles=Roles de Cliente +select-client-to-view-roles=Selecciona el cliente para ver sus roles +available-roles.tooltip=Roles de este cliente que puedes asociar a este rol compuesto. +client.associated-roles.tooltip=Roles de cliente asociados con este rol compuesto. +add-builtin=A\u00F1adir Builtin +category=Categor\u00EDa +type=Tipo +no-mappers-available=No hay asignadores disponibles +add-builtin-protocol-mappers=A\u00F1adir Builtin Protocol Mappers +add-builtin-protocol-mapper=A\u00F1adir Builtin Protocol Mapper +scope-mappings=Asignaciones de \u00E1mbito +full-scope-allowed=Permitir todos los \u00E1mbitos +full-scope-allowed.tooltip=Permite deshabilitar todas las restricciones. +scope.available-roles.tooltip=Roles de dominio que pueden ser asignados al \u00E1mbito +assigned-roles=Roles Asignados +assigned-roles.tooltip=Roles a nivel de dominio asignados a este \u00E1mbito. +effective-roles=Roles Efectivos +realm.effective-roles.tooltip=Roles de dominio asignados que pueden haber sido heredados de un rol compuesto. +select-client-roles.tooltip=Selecciona el cliente para ver sus roles +assign.available-roles.tooltip=Roles de clientes disponibles para ser asignados. +client.assigned-roles.tooltip=Roles de cliente asignados +client.effective-roles.tooltip=Roles de cliente asignados que pueden haber sido heredados desde un rol compuesto. +basic-configuration=Configuraci\u00F3n b\u00E1sica +node-reregistration-timeout=Tiempo de espera de re-registro de nodo +node-reregistration-timeout.tooltip=Indica el m\u00E1ximo intervalo de tiempo para que los nodos del cluster registrados se vuelvan a registrar. Si el nodo del cluster no env\u00EDa una petici\u00F3n de re-registro a Keycloak dentro de este intervalo, ser\u00E1 desregistrado de Keycloak +registered-cluster-nodes=Registrar nodos de cluster +register-node-manually=Registrar nodo manualmente +test-cluster-availability=Probar disponibilidad del cluster +last-registration=\u00DAltimo registro +node-host=Host del nodo +no-registered-cluster-nodes=No hay nodos de cluster registrados disponibles +cluster-nodes=Nodos de cluster +add-node=A\u00F1adir Nodo +active-sessions.tooltip=N\u00FAmero total de sesiones activas para este cliente. +show-sessions=Mostrar sesiones +show-sessions.tooltip=Advertencia, esta es una operaci\u00F3n potencialmente costosa dependiendo del n\u00FAmero de sesiones activas. +user=Usuario +from-ip=Desde IP +session-start=Inicio de sesi\u00F3n +first-page=Primera p\u00E1gina +previous-page=P\u00E1gina Anterior +next-page=P\u00E1gina siguiente +client-revoke.not-before.tooltip=Revocar todos los tokens emitidos antes de esta fecha para este cliente. +client-revoke.push.tooltip=Si la URL de administraci\u00F3n est\u00E1 configurada para este cliente, env\u00EDa esta pol\u00EDtica a este cliente. +select-a-format=Selecciona un formato +download=Descargar +offline-tokens=Tokens sin conexi\u00F3n +offline-tokens.tooltip=N\u00FAmero total de tokens sin conexi\u00F3n de este cliente. +show-offline-tokens=Mostrar tokens sin conexi\u00F3n +show-offline-tokens.tooltip=Advertencia, esta es una operaci\u00F3n potencialmente costosa dependiendo del n\u00FAmero de tokens sin conexi\u00F3n. +token-issued=Token expedido +last-access=\u00DAltimo Acceso +last-refresh=\u00DAltima actualizaci\u00F3n +key-export=Exportar clave +key-import=Importar clave +export-saml-key=Exportar clave SAML +import-saml-key=Importar clave SAML +realm-certificate-alias=Alias del certificado del dominio +realm-certificate-alias.tooltip=El certificado del dominio es almacenado en archivo. Este es el alias al mismo. +signing-key=Clave de firma +saml-signing-key=Clave de firma SAML. +private-key=Clave Privada +generate-new-keys=Generar nuevas claves +export=Exportar +encryption-key=Clave de cifrado +saml-encryption-key.tooltip=Clave de cifrado de SAML +service-accounts=Cuentas de servicio +service-account.available-roles.tooltip=Roles de dominio que pueden ser asignados a la cuenta del servicio. +service-account.assigned-roles.tooltip=Roles de dominio asignados a la cuenta del servicio. +service-account-is-not-enabled-for=La cuenta del servicio no est\u00E1 habilitada para {{client}} +create-protocol-mappers=Crear asignadores de protocolo +create-protocol-mapper=Crear asignador de protocolo +protocol=Protocolo +protocol.tooltip=Protocolo. +id=ID +mapper.name.tooltip=Nombre del asignador. +mapper.consent-required.tooltip=Cuando se concede acceso temporal, \u00BFes necesario el consentimiento del usuario para proporcinar estos datos al cliente cliente? +consent-text=Texto del consentimiento +consent-text.tooltip=Texto para mostrar en la p\u00E1gina de consentimiento. +mapper-type=Tipo de asignador + +# realm identity providers +identity-providers=Proveedores de identidad +table-of-identity-providers=Tabla de proveedores de identidad +add-provider.placeholder=A\u00F1adir proveedor... +provider=Proveedor +gui-order=Orden en la interfaz gr\u00E1fica (GUI) +redirect-uri=URI de redirecci\u00F3n +redirect-uri.tooltip=La URI de redirecci\u00F3n usada para configurar el proveedor de identidad. +alias=Alias +identity-provider.alias.tooltip=El alias que identifica de forma \u00FAnica un proveedor de identidad, se usa tambi\u00E9n para construir la URI de redirecci\u00F3n. +identity-provider.enabled.tooltip=Habilita/deshabilita este proveedor de identidad. +authenticate-by-default=Autenticar por defecto +identity-provider.authenticate-by-default.tooltip=Indica si este proveedor deber\u00EDa ser probado por defecto para autenticacaci\u00F3n incluso antes de mostrar la p\u00E1gina de inicio de sesi\u00F3n. +store-tokens=Almacenar tokens +identity-provider.store-tokens.tooltip=Hablitar/deshabilitar si los tokens deben ser almacenados despu\u00E9s de autenticar a los usuarios. +stored-tokens-readable=Tokens almacenados legibles +identity-provider.stored-tokens-readable.tooltip=Habilitar/deshabilitar is los nuevos usuarios pueden leear los tokens almacenados. Esto asigna el rol 'broker.read-token'. +update-profile-on-first-login=Actualizar perfil en el primer inicio de sesi\u00F3n +on=Activado +on-missing-info=Si falta informaci\u00F3n +off=Desactivado +update-profile-on-first-login.tooltip=Define condiciones bajos las cuales un usuario tiene que actualizar su perfil durante el primer inicio de sesi\u00F3n. +trust-email=Confiar en el email +trust-email.tooltip=Si est\u00E1 habilitado, el email recibido de este proveedor no se verificar\u00E1 aunque la verificaci\u00F3n est\u00E9 habilitada para el dominio. +gui-order.tooltip=N\u00FAmero que define el orden del proveedor en la interfaz gr\u00E1fica (GUI) (ej. en la p\u00E1gina de inicio de sesi\u00F3n) +openid-connect-config=Configuraci\u00F3n de OpenID Connect +openid-connect-config.tooltip=Configuraci\u00F3n de OIDC SP e IDP externos +authorization-url=URL de autorizaci\u00F3n +authorization-url.tooltip=La URL de autorizaci\u00F3n. +token-url=Token URL +token-url.tooltip=La URL del token. +logout-url=URL de desconexi\u00F3n +identity-provider.logout-url.tooltip=Punto de cierre de sesi\u00F3n para usar en la desconexi\u00F3n de usuarios desde un proveedor de identidad (IDP) externo. +backchannel-logout=Backchannel Logout +backchannel-logout.tooltip=Does the external IDP support backchannel logout? +user-info-url=URL de informaci\u00F3n de usuario +user-info-url.tooltip=.La URL de informaci\u00F3n de usuario. Opcional +identity-provider.client-id.tooltip=El cliente o identificador de cliente registrado en el proveedor de identidad. +client-secret=Secreto de Cliente +show-secret=Mostrar secreto +hide-secret=Ocultar secreto +client-secret.tooltip=El cliente o el secreto de cliente registrado en el proveedor de identidad. +issuer=Emisor +issuer.tooltip=El identificador del emisor para el emisor de la respuesta. Si no se indica, no se realizar\u00E1 ninguna validaci\u00F3n. +default-scopes=\u00C1mbitos por defecto +identity-provider.default-scopes.tooltip=Los \u00E1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Puede ser una lista de \u00E1mbitos separados por espacios. El valor por defecto es 'openid'. +prompt=Prompt +unspecified.option=no especificado +none.option=ninguno +consent.option=consentimiento +login.option=login +select-account.option=select_account +prompt.tooltip=Indica si el servidor de autorizaci\u00F3n solicita al usuario final para reautenticaci\u00F3n y consentimiento. +validate-signatures=Validar firmas +identity-provider.validate-signatures.tooltip=Habilitar/deshabilitar la validaci\u00F3n de firmas de proveedores de identidad (IDP) externos +validating-public-key=Validando clave p\u00FAblica +identity-provider.validating-public-key.tooltip=La clave p\u00FAblica en formato PEM que debe usarse para verificar las firmas de proveedores de identidad (IDP) externos. +import-external-idp-config=Importar configuraci\u00F3n externa de IDP +import-external-idp-config.tooltip=Te permite cargar metadatos de un proveedor de identidad (IDP) externo de un archivo de coniguraci\u00F3n o descargarlo desde una URL. +import-from-url=Importar desde URL +identity-provider.import-from-url.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) remoto. +import-from-file=Importar desde archivo +identity-provider.import-from-file.tooltip=Importar metadatos desde un descriptor de un proveedor de identidad (IDP) descargado. +saml-config=Configuraci\u00F3n SAML +identity-provider.saml-config.tooltip=Configurci\u00F3n de proveedor SAML e IDP externo +single-signon-service-url=URL de servicio de conexi\u00F3n \u00FAnico (SSO) +saml.single-signon-service-url.tooltip=La URL que debe ser usada para enviar peticiones de autenticaci\u00F3n (SAML AuthnRequest). +single-logout-service-url=URL de servicio de desconexi\u00F3n \u00FAnico +saml.single-logout-service-url.tooltip=La URL que debe usarse para enviar peticiones de desconexi\u00F3n. +nameid-policy-format=Formato de pol\u00EDtica NameID +nameid-policy-format.tooltip=Indica la referencia a la URI correspondiente a un formato de NameID. El valor por defecto es urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. +http-post-binding-response=HTTP-POST enlace de respuesta +http-post-binding-response.tooltip=Indica si se reponde a las peticiones usando HTTP-POST. Si no est\u00E1 activado, se usa HTTP-REDIRECT. +http-post-binding-for-authn-request=HTTP-POST para AuthnRequest +http-post-binding-for-authn-request.tooltip=Indica si AuthnRequest debe ser envianda usando HTTP-POST. Si no est\u00E1 activado se hace HTTP-REDIRECT. +want-authn-requests-signed=Firmar AuthnRequests +want-authn-requests-signed.tooltip=Indica si el proveedor de identidad espera recibir firmadas las AuthnRequest. +force-authentication=Forzar autenticaci\u00F3n +identity-provider.force-authentication.tooltip=Indica si el proveedor de identidad debe autenticar al presentar directamente las credenciales en lugar de depender de un contexto de seguridad previo. +validate-signature=Validar firma +saml.validate-signature.tooltip=Habilitar/deshabilitar la validaci\u00F3n de firma en respuestas SAML. +validating-x509-certificate=Validando certificado X509 +validating-x509-certificate.tooltip=El certificado en formato PEM que debe usarse para comprobar las firmas. +saml.import-from-url.tooltip=Importar metadatos desde un descriptor de entidad remoto de un IDP de SAML +social.client-id.tooltip=El identificador del cliente registrado con el proveedor de identidad. +social.client-secret.tooltip=El secreto del cliente registrado con el proveedor de identidad. +social.default-scopes.tooltip=\u00C1mbitos que se enviar\u00E1n cuando se solicite autorizaci\u00F3n. Ver la documentaci\u00F3n para los posibles valores, separador y valor por defecto. +key=Clave +stackoverflow.key.tooltip=La clave obtenida en el registro del cliente de Stack Overflow. + +realms=Dominios +realm=Dominio + +identity-provider-mappers=Asignadores de proveedores de identidad (IDP) +create-identity-provider-mapper=Crear asignador de proveedor de identidad (IDP) +add-identity-provider-mapper=A\u00F1adir asignador de proveedor de identidad +client.description.tooltip=Indica la descripci\u00F3n del cliente. Por ejemplo 'My Client for TimeSheets'. Tambi\u00E9n soporta claves para valores localizzados. Por ejemplo: ${my_client_description} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties new file mode 100644 index 0000000000..b5b7ca80b9 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/messages_es.properties @@ -0,0 +1,8 @@ +invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}. +invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas. +invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos. +invalidPasswordMinUpperCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras may\u00FAsculas. +invalidPasswordMinSpecialCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} caracteres especiales. +invalidPasswordNotUsernameMessage=Contrase\u00F1a incorrecta: no puede ser igual al nombre de usuario. +invalidPasswordRegexPatternMessage=Contrase\u00F1a incorrecta: no cumple la expresi\u00F3n regular. +invalidPasswordHistoryMessage=Contrase\u00F1a incorrecta: no puede ser igual a ninguna de las \u00FAltimas {0} contrase\u00F1as. diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index f1d922b31a..bfbcee9c5b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -7,7 +7,7 @@ var configUrl = consoleBaseUrl + "/config"; var auth = {}; -var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']); +var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'angularTreeview', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']); var resourceRequests = 0; var loadingTimer = -1; @@ -176,6 +176,27 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RealmTokenDetailCtrl' }) + .when('/realms/:realm/client-initial-access', { + templateUrl : resourceUrl + '/partials/client-initial-access.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientInitialAccess : function(ClientInitialAccessLoader) { + return ClientInitialAccessLoader(); + } + }, + controller : 'ClientInitialAccessCtrl' + }) + .when('/realms/:realm/client-initial-access/create', { + templateUrl : resourceUrl + '/partials/client-initial-access-create.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'ClientInitialAccessCreateCtrl' + }) .when('/realms/:realm/keys-settings', { templateUrl : resourceUrl + '/partials/realm-keys.html', resolve : { @@ -199,6 +220,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, providerFactory : function(IdentityProviderFactoryLoader) { return {}; + }, + authFlows : function(AuthenticationFlowsLoader) { + return {}; } }, controller : 'RealmIdentityProviderCtrl' @@ -217,6 +241,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, providerFactory : function(IdentityProviderFactoryLoader) { return new IdentityProviderFactoryLoader(); + }, + authFlows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); } }, controller : 'RealmIdentityProviderCtrl' @@ -235,6 +262,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, providerFactory : function(IdentityProviderFactoryLoader) { return IdentityProviderFactoryLoader(); + }, + authFlows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); } }, controller : 'RealmIdentityProviderCtrl' @@ -438,6 +468,21 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'UserRoleMappingCtrl' }) + .when('/realms/:realm/users/:user/groups', { + templateUrl : resourceUrl + '/partials/user-group-membership.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + groups : function(GroupListLoader) { + return GroupListLoader(); + } + }, + controller : 'UserGroupMembershipCtrl' + }) .when('/realms/:realm/users/:user/sessions', { templateUrl : resourceUrl + '/partials/user-sessions.html', resolve : { @@ -574,6 +619,97 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RoleListCtrl' }) + .when('/realms/:realm/groups', { + templateUrl : resourceUrl + '/partials/group-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + groups : function(GroupListLoader) { + return GroupListLoader(); + } + }, + controller : 'GroupListCtrl' + }) + .when('/create/group/:realm/parent/:parentId', { + templateUrl : resourceUrl + '/partials/create-group.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + parentId : function($route) { + return $route.current.params.parentId; + } + }, + controller : 'GroupCreateCtrl' + }) + .when('/realms/:realm/groups/:group', { + templateUrl : resourceUrl + '/partials/group-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupDetailCtrl' + }) + .when('/realms/:realm/groups/:group/attributes', { + templateUrl : resourceUrl + '/partials/group-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupDetailCtrl' + }) + .when('/realms/:realm/groups/:group/members', { + templateUrl : resourceUrl + '/partials/group-members.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupMembersCtrl' + }) + .when('/realms/:realm/groups/:group/role-mappings', { + templateUrl : resourceUrl + '/partials/group-role-mappings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + client : function() { + return {}; + } + }, + controller : 'GroupRoleMappingCtrl' + }) + .when('/realms/:realm/default-groups', { + templateUrl : resourceUrl + '/partials/default-groups.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + groups : function(GroupListLoader) { + return GroupListLoader(); + } + }, + controller : 'DefaultGroupsCtrl' + }) + .when('/create/role/:realm/clients/:client', { templateUrl : resourceUrl + '/partials/client-role-detail.html', @@ -1339,12 +1475,15 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RealmOtpPolicyCtrl' }) - .when('/realms/:realm/authentication/config/:provider/:config', { + .when('/realms/:realm/authentication/flows/:flow/config/:provider/:config', { templateUrl : resourceUrl + '/partials/authenticator-config.html', resolve : { realm : function(RealmLoader) { return RealmLoader(); }, + flow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, configType : function(AuthenticationConfigDescriptionLoader) { return AuthenticationConfigDescriptionLoader(); }, @@ -1354,12 +1493,15 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'AuthenticationConfigCtrl' }) - .when('/create/authentication/:realm/execution/:executionId/provider/:provider', { + .when('/create/authentication/:realm/flows/:flow/execution/:executionId/provider/:provider', { templateUrl : resourceUrl + '/partials/authenticator-config.html', resolve : { realm : function(RealmLoader) { return RealmLoader(); }, + flow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, configType : function(AuthenticationConfigDescriptionLoader) { return AuthenticationConfigDescriptionLoader(); }, @@ -1856,6 +1998,24 @@ module.directive('kcTabsUser', function () { } }); +module.directive('kcTabsGroup', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-group.html' + } +}); + +module.directive('kcTabsGroupList', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-group-list.html' + } +}); + module.directive('kcTabsClient', function () { return { scope: true, diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index e35e1c62d2..971b0c47a8 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -30,7 +30,7 @@ module.controller('ClientRoleListCtrl', function($scope, $location, realm, clien }); }); -module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client) { +module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client, ClientRegistrationAccessToken, Notifications) { $scope.realm = realm; $scope.client = angular.copy(client); $scope.clientAuthenticatorProviders = clientAuthenticatorProviders; @@ -68,6 +68,17 @@ module.controller('ClientCredentialsCtrl', function($scope, $location, realm, cl } }, true); + $scope.regenerateRegistrationAccessToken = function() { + var secret = ClientRegistrationAccessToken.update({ realm : $scope.realm.realm, client : $scope.client.id }, + function(data) { + Notifications.success('The registration access token has been updated.'); + $scope.client['registrationAccessToken'] = data.registrationAccessToken; + }, + function() { + Notifications.error('Failed to update the registration access token'); + } + ); + }; }); module.controller('ClientSecretCtrl', function($scope, $location, ClientSecret, Notifications) { @@ -877,7 +888,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se $scope.viewImportDetails = function() { $modal.open({ templateUrl: resourceUrl + '/partials/modal/view-object.html', - controller: 'JsonModalCtrl', + controller: 'ObjectModalCtrl', resolve: { object: function () { return $scope.client; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js new file mode 100755 index 0000000000..c336dc8680 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js @@ -0,0 +1,428 @@ +module.controller('GroupListCtrl', function($scope, $route, realm, groups, Groups, Group, GroupChildren, Notifications, $location, Dialog) { + $scope.realm = realm; + $scope.groupList = [ + {"id" : "realm", "name": "Groups", + "subGroups" : groups} + ]; + + $scope.tree = []; + + $scope.edit = function(selected) { + $location.url("/realms/" + realm.realm + "/groups/" + selected.id); + } + + $scope.cut = function(selected) { + $scope.cutNode = selected; + } + + $scope.isDisabled = function() { + if (!$scope.tree.currentNode) return true; + return $scope.tree.currentNode.id == 'realm'; + } + + $scope.paste = function(selected) { + if (selected == null) return; + if ($scope.cutNode == null) return; + if (selected.id == 'realm') { + Groups.save({realm: realm.realm}, {id:$scope.cutNode.id}, function() { + $route.reload(); + Notifications.success("Group moved."); + + }); + + } else { + GroupChildren.save({realm: realm.realm, groupId: selected.id}, {id:$scope.cutNode.id}, function() { + $route.reload(); + Notifications.success("Group moved."); + + }); + + } + + } + + $scope.remove = function(selected) { + if (selected == null) return; + Dialog.confirmDelete(selected.name, 'group', function() { + Group.remove({ realm: realm.realm, groupId : selected.id }, function() { + $route.reload(); + Notifications.success("The group has been deleted."); + }); + }); + + } + + $scope.createGroup = function(selected) { + var parent = 'realm'; + if (selected) { + parent = selected.id; + } + $location.url("/create/group/" + realm.realm + '/parent/' + parent); + + } + var isLeaf = function(node) { + return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); + } + + $scope.getGroupClass = function(node) { + if (node.id == "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + } + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + return 'cut'; + } + return undefined; + } + +}); + +module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, Groups, Group, GroupChildren, Notifications, $location) { + $scope.realm = realm; + $scope.group = {}; + $scope.save = function() { + console.log('save!!!'); + if (parentId == 'realm') { + console.log('realm') + Groups.save({realm: realm.realm}, $scope.group, function(data, headers) { + var l = headers().location; + + + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/groups/" + id); + Notifications.success("Group Created."); + }) + + } else { + GroupChildren.save({realm: realm.realm, groupId: parentId}, $scope.group, function(data, headers) { + var l = headers().location; + + + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/groups/" + id); + Notifications.success("Group Created."); + }) + + } + + } + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/groups"); + }; +}); + +module.controller('GroupTabCtrl', function(Dialog, $scope, Current, Group, Notifications, $location) { + $scope.removeGroup = function() { + Dialog.confirmDelete($scope.group.name, 'group', function() { + Group.remove({ + realm : Current.realm.realm, + groupId : $scope.group.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/groups"); + Notifications.success("The group has been deleted."); + }); + }); + }; +}); + +module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Group, Notifications, $location) { + $scope.realm = realm; + + if (!group.attributes) { + group.attributes = {} + } + convertAttributeValuesToString(group); + + + $scope.group = angular.copy(group); + + $scope.changed = false; // $scope.create; + $scope.$watch('group', function() { + if (!angular.equals($scope.group, group)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + convertAttributeValuesToLists(); + + Group.update({ + realm: realm.realm, + groupId: $scope.group.id + }, $scope.group, function () { + $scope.changed = false; + convertAttributeValuesToString($scope.group); + group = angular.copy($scope.group); + Notifications.success("Your changes have been saved to the group."); + }); + }; + + function convertAttributeValuesToLists() { + var attrs = $scope.group.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + var attrVals = attrs[attribute].split("##"); + attrs[attribute] = attrVals; + } + } + } + + function convertAttributeValuesToString(group) { + var attrs = group.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + var attrVals = attrs[attribute].join("##"); + attrs[attribute] = attrVals; + } + } + } + + $scope.reset = function() { + $scope.group = angular.copy(group); + $scope.changed = false; + }; + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/groups"); + }; + + $scope.addAttribute = function() { + $scope.group.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.group.attributes[key]; + } +}); + +module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, clients, client, Notifications, GroupRealmRoleMapping, + GroupClientRoleMapping, GroupAvailableRealmRoleMapping, GroupAvailableClientRoleMapping, + GroupCompositeRealmRoleMapping, GroupCompositeClientRoleMapping) { + $scope.realm = realm; + $scope.group = group; + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.clients = clients; + $scope.client = client; + $scope.clientRoles = []; + $scope.clientComposite = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + $scope.dummymodel = []; + + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + + $scope.addRealmRole = function() { + var roles = $scope.selectedRealmRoles; + $scope.selectedRealmRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm', + roles).success(function() { + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.targetClient) { + console.log('load available'); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + Notifications.success("Role mappings updated."); + + }); + }; + + $scope.deleteRealmRole = function() { + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm', + {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() { + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.targetClient) { + console.log('load available'); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + Notifications.success("Role mappings updated."); + }); + }; + + $scope.addClientRole = function() { + $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id, + $scope.selectedClientRoles).success(function() { + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + Notifications.success("Role mappings updated."); + }); + }; + + $scope.deleteClientRole = function() { + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id, + {data : $scope.selectedClientMappings, headers : {"content-type" : "application/json"}}).success(function() { + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + Notifications.success("Role mappings updated."); + }); + }; + + + $scope.changeClient = function() { + if ($scope.targetClient) { + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + } else { + $scope.clientRoles = null; + $scope.clientMappings = null; + $scope.clientComposite = null; + } + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + }; + + + +}); + +module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMembership) { + $scope.realm = realm; + $scope.page = 0; + $scope.group = group; + + $scope.query = { + realm: realm.realm, + groupId: group.id, + max : 5, + first : 0 + } + + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + console.log("query.search: " + $scope.query.search); + $scope.searchLoaded = false; + + $scope.users = GroupMembership.query($scope.query, function() { + console.log('search loaded'); + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); + +}); + +module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, DefaultGroups, Notifications, $location, Dialog) { + $scope.realm = realm; + $scope.groupList = groups; + $scope.selectedGroup = null; + $scope.tree = []; + + DefaultGroups.query({realm: realm.realm}, function(data) { + $scope.defaultGroups = data; + + }); + + $scope.addDefaultGroup = function() { + if (!$scope.tree.currentNode) { + Notifications.error('Please select a group to add'); + return; + }; + + DefaultGroups.update({realm: realm.realm, groupId: $scope.tree.currentNode.id}, function() { + Notifications.success('Added default group'); + $route.reload(); + }); + + }; + + $scope.removeDefaultGroup = function() { + DefaultGroups.remove({realm: realm.realm, groupId: $scope.selectedGroup.id}, function() { + Notifications.success('Removed default group'); + $route.reload(); + }); + + }; + + var isLeaf = function(node) { + return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); + }; + + $scope.getGroupClass = function(node) { + if (node.id == "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + } + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + return 'cut'; + } + return undefined; + } + +}); + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 4a4fe89c5d..a837f4f45f 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -337,7 +337,7 @@ module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serv $scope.supportedLocalesOptions = { 'multiple' : true, 'simple_tags' : true, - 'tags' : ['en', 'de', 'pt-BR', 'it'] + 'tags' : ['en', 'de', 'pt-BR', 'it', 'es'] }; $scope.$watch('realm.supportedLocales', function(oldVal, newVal) { @@ -594,20 +594,11 @@ module.controller('IdentityProviderTabCtrl', function(Dialog, $scope, Current, N }; }); -module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, $location, Notifications, Dialog) { +module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, authFlows, $location, Notifications, Dialog) { console.log('RealmIdentityProviderCtrl'); $scope.realm = angular.copy(realm); - $scope.initProvider = function() { - if (instance && instance.alias) { - - } else { - $scope.identityProvider.updateProfileFirstLoginMode = "on"; - } - - }; - $scope.initSamlProvider = function() { $scope.nameIdFormats = [ /* @@ -658,7 +649,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload } else { $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format; $scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1]; - $scope.identityProvider.updateProfileFirstLoginMode = "off"; } } @@ -676,8 +666,8 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload $scope.identityProvider.alias = providerFactory.id; $scope.identityProvider.providerId = providerFactory.id; $scope.identityProvider.enabled = true; - $scope.identityProvider.updateProfileFirstLoginMode = "off"; $scope.identityProvider.authenticateByDefault = false; + $scope.identityProvider.firstBrokerLoginFlowAlias = 'first broker login'; $scope.newIdentityProvider = true; } @@ -696,6 +686,13 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload $scope.configuredProviders = angular.copy(realm.identityProviders); + $scope.authFlows = []; + for (var i=0 ; iDelete
  • Add Execution
  • Add Flow
  • -
  • Config
  • -
  • Config
  • +
  • Config
  • +
  • Config
  • diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html index 585680616b..f5875683e7 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authenticator-config.html @@ -2,6 +2,7 @@ diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html index 631939c9bf..1d59078d1a 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-generic.html @@ -1,5 +1,5 @@
    -
    +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html index aa032033dc..8c581d7bc2 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-jwt.html @@ -1,5 +1,5 @@
    - +
    {{:: 'certificate.tooltip' | translate}} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html index 2bd53dbedf..744ea80dac 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials-secret.html @@ -1,5 +1,5 @@
    - +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html index 96f16cad72..b1b1062dfb 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-credentials.html @@ -28,6 +28,11 @@
    +
    + +
    +
    +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html new file mode 100755 index 0000000000..ef549396ab --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access-create.html @@ -0,0 +1,63 @@ +
    + + + +

    {{:: 'add-client' | translate}}

    + + + +
    + + +
    + + +
    + {{:: 'expiration.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'count.tooltip' | translate}} +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + + {{:: 'initial-access.copyPaste.tooltip' | translate}} +
    + +
    +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html new file mode 100755 index 0000000000..7b7c90e72d --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html @@ -0,0 +1,52 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    {{:: 'id' | translate}}{{:: 'created' | translate}}{{:: 'expires' | translate}}{{:: 'count' | translate}}{{:: 'remainingCount' | translate}}{{:: 'actions' | translate}}
    {{ia.id}}{{(ia.timestamp * 1000)|date:'shortDate'}} {{(ia.timestamp * 1000)|date:'mediumTime'}}{{((ia.timestamp + ia.expiration) * 1000)|date:'shortDate'}} {{((ia.timestamp + ia.expiration) * 1000)|date:'mediumTime'}}{{ia.count}}{{ia.remainingCount}} + +
    {{:: 'no-results' | translate}}{{:: 'no-clients-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html index 4237f7dce4..04c2339238 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html @@ -37,7 +37,7 @@ {{mapper.name}} {{mapperTypes[mapper.protocolMapper].category}} {{mapperTypes[mapper.protocolMapper].name}} - + {{:: 'no-mappers-available' | translate}} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html new file mode 100644 index 0000000000..55a3546dbd --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-registration-access-token.html @@ -0,0 +1,18 @@ +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    + {{:: 'registrationAccessToken.tooltip' | translate}} +
    +
    +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html index 53c82d19e7..78eb532175 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html @@ -11,7 +11,7 @@
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html index e884ef8630..e29eef417d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-flow-execution.html @@ -7,7 +7,7 @@
    - +
    Specifies display name for the flow.
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-group.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-group.html new file mode 100755 index 0000000000..6e4788016c --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-group.html @@ -0,0 +1,25 @@ +
    +
    +

    Create Group

    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/default-groups.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/default-groups.html new file mode 100755 index 0000000000..bcfd547da4 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/default-groups.html @@ -0,0 +1,80 @@ +
    + + +
    +
    + + +
    +
    +
    + + + + + + + + + + + +
    +
    + + Newly created or registered users will automatically be added to these groups + +
    + +
    +
    +
    + + + +
    +
    +
    + + + + + + + + + + + +
    + +
    + + Select a group you want to add as a default. + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html new file mode 100755 index 0000000000..9fcfc8b741 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html @@ -0,0 +1,45 @@ +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    KeyValueActions
    {{key}} + +
    + +
    + +
    +
    + + +
    +
    +
    +
    + + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html new file mode 100755 index 0000000000..3dc41aa59e --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-detail.html @@ -0,0 +1,28 @@ +
    + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-list.html new file mode 100755 index 0000000000..ac8267cd43 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-list.html @@ -0,0 +1,37 @@ +
    + + + + + + + + + + + + + +
    +
    +
    + + + + + +
    +
    +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html new file mode 100755 index 0000000000..50bc11b008 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html @@ -0,0 +1,50 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    UsernameLast NameFirst NameEmail
    +
    + + + +
    +
    {{user.username}}{{user.lastName}}{{user.firstName}}{{user.email}} + +
    No group membersNo group members
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html new file mode 100755 index 0000000000..22916e1217 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html @@ -0,0 +1,101 @@ +
    + + + + +
    +
    + + +
    +
    +
    + + + + Realm roles that can be assigned to the group. +
    +
    + + Realm roles mapped to the group + + +
    +
    + + All realm role mappings. Some roles here might be inherited from a mapped composite role. + +
    +
    +
    +
    + +
    + +
    +
    +
    Select client to view roles for client
    +
    +
    +
    + + Assignable roles from this client. + + +
    +
    + + Role mappings for this client. + + +
    +
    + + +
    +
    +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html index 85ad20bb88..eaaf8116b0 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html @@ -46,23 +46,23 @@
    - +
    How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?
    -
    +
    - +
    What should the initial counter value be?
    -
    +
    - +
    How many seconds should an OTP token be valid? Defaults to 30 seconds.
    @@ -79,4 +79,4 @@
    - \ No newline at end of file + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 170bfe65b4..4cbd12a09f 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -1,4 +1,4 @@ -
    +
    {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}
    -
    - -
    -
    - -
    -
    - {{:: 'update-profile-on-first-login.tooltip' | translate}} -
    @@ -79,6 +66,19 @@
    {{:: 'gui-order.tooltip' | translate}}
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    {{:: 'openid-connect-config' | translate}} {{:: 'openid-connect-config.tooltip' | translate}} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index bb9726f6e9..c8f6c3771a 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -52,19 +52,6 @@
    {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}
    -
    - -
    -
    - -
    -
    - {{:: 'update-profile-on-first-login.tooltip' | translate}} -
    @@ -79,6 +66,19 @@
    {{:: 'gui-order.tooltip' | translate}}
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    {{:: 'saml-config' | translate}} {{:: 'identity-provider.saml-config.tooltip' | translate}} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html index 2f71d197be..5897f9972b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html @@ -1,4 +1,4 @@ -
    +
    {{:: 'identity-provider.enabled.tooltip' | translate}}
    -
    - -
    -
    - -
    -
    - {{:: 'update-profile-on-first-login.tooltip' | translate}} -
    @@ -97,6 +84,19 @@
    {{:: 'gui-order.tooltip' | translate}}
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html index 0161a3011e..8a81237f4d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html @@ -20,8 +20,8 @@ {{requiredAction.name}} - - + + No required actions configured diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html index 7904e56b1c..d81bea7c08 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html @@ -94,7 +94,6 @@
    - diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html new file mode 100755 index 0000000000..a8e2c131c9 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html @@ -0,0 +1,85 @@ +
    + + + + +
    +
    + + +
    +
    +
    + + + + + + + + + + + +
    +
    + + Groups user is a member of. Select a listed group and click the Leave button to leave the group. + +
    + +
    +
    +
    + + + +
    +
    +
    + + + + + + + + + + + +
    + +
    + + Groups a user can join. Select a group and click the join button. + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html index baa3b45d5e..8da0e56148 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html @@ -40,7 +40,8 @@ \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html old mode 100644 new mode 100755 index edc3a66481..7508c83a53 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html @@ -10,6 +10,7 @@
  • Attributes
  • Credentials
  • Role Mappings
  • +
  • Groups
  • Consents
  • Sessions
  • Identity Provider Links
  • diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl new file mode 100644 index 0000000000..f77eb38bfa --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl @@ -0,0 +1,21 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=false; section> + <#if section = "title"> + ${msg("confirmLinkIdpTitle")} + <#elseif section = "header"> + ${msg("confirmLinkIdpTitle")} + <#elseif section = "form"> +
    +

    ${message.summary}

    +
    + +
    + +
    + + +
    + +
    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl new file mode 100644 index 0000000000..0ba068603c --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl @@ -0,0 +1,15 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "title"> + ${msg("emailLinkIdpTitle", idpAlias)} + <#elseif section = "header"> + ${msg("emailLinkIdpTitle", idpAlias)} + <#elseif section = "form"> +

    + ${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)} +

    +

    + ${msg("emailLinkIdp2")} ${msg("doClickHere")} ${msg("emailLinkIdp3")} +

    + + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl index 584bea322b..458884c849 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/login-update-profile.ftl @@ -6,7 +6,7 @@ ${msg("loginProfileTitle")} <#elseif section = "form">
    - <#if realm.editUsernameAllowed> + <#if user.editUsernameAllowed>
    diff --git a/forms/common-themes/src/main/resources/theme/base/login/login.ftl b/forms/common-themes/src/main/resources/theme/base/login/login.ftl index 925f99c207..5786807722 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/login.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/login.ftl @@ -13,7 +13,11 @@
    - + <#if usernameEditDisabled??> + + <#else> + +
    @@ -29,7 +33,7 @@
    - <#if realm.rememberMe> + <#if realm.rememberMe && !usernameEditDisabled??>