resolve conflicts

This commit is contained in:
Bill Burke 2015-11-11 18:06:39 -05:00
commit 33ac048c8c
156 changed files with 5143 additions and 1262 deletions

View file

@ -92,4 +92,9 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
}
@Override
public IdentityProviderDataMarshaller getMarshaller() {
return new DefaultDataMarshaller();
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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> T deserialize(String serialized, Class<T> 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);
}
}
}
}

View file

@ -103,4 +103,10 @@ public interface IdentityProvider<C extends IdentityProviderModel> 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();
}

View file

@ -0,0 +1,12 @@
package org.keycloak.broker.provider;
/**
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface IdentityProviderDataMarshaller {
String serialize(Object obj);
<T> T deserialize(String serialized, Class<T> clazz);
}

View file

@ -45,6 +45,11 @@
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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> T deserialize(String serialized, Class<T> 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);
}
}
}

View file

@ -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 AbstractIdentityProvider<SAMLIdentityP
return SignatureAlgorithm.RSA_SHA256;
}
@Override
public IdentityProviderDataMarshaller getMarshaller() {
return new SAMLDataMarshaller();
}
}

View file

@ -0,0 +1,64 @@
package org.keycloak.broker.saml;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SAMLDataMarshallerTest {
private static final String TEST_RESPONSE = "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_4804cf50-cd96-4b92-823e-89adaa0c78ba\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.920Z\" Destination=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\" InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"></samlp:StatusCode></samlp:Status><saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
private static final String TEST_ASSERTION = "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>";
private static final String TEST_AUTHN_TYPE = "<saml:AuthnStatement xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
@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);
}
}

View file

@ -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;
}

View file

@ -37,6 +37,14 @@
<constraints nullable="false"/>
</column>
</createTable>
<addColumn tableName="IDENTITY_PROVIDER">
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</addColumn>
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="KEYCLOAK_GROUP" constraintName="FK_GROUP_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
@ -51,6 +59,5 @@
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
</changeSet>
</databaseChangeLog>

View file

@ -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<String, String> config = new HashMap<String, String>();
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;
}

View file

@ -9,7 +9,7 @@
<fileSets>
<fileSet>
<directory>../../target/site/apidocs</directory>
<directory>target/site/apidocs</directory>
<outputDirectory>javadocs</outputDirectory>
</fileSet>
<fileSet>

View file

@ -1,11 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<style>
td { padding: 5px; }
</style>
</head>
<body>
<h1>Keyloak Documentation</h1>
<ul>
<li><a href="userguide/keycloak-server/html/index.html">Server and Keycloak Adapter Userguide HTML</a></li>
<li><a href="userguide/keycloak-server/html_single/index.html">Server and Keycloak Adapter Userguide HTML Single Page</a></li>
<li><a href="userguide/keycloak-server/pdf/keycloak-reference-guide-en-US.pdf">Server and Keycloak Adapter Userguide PDF</a></li>
<li><a href="userguide/saml-client-adapter/html/index.html">SAML Client Adapter Userguide HTML</a></li>
<li><a href="userguide/saml-client-adapter/html_single/index.html">>SAML Client Adapter Userguide HTML Single Page</a></li>
<li><a href="userguide/saml-client-adapter/pdf/keycloak-reference-guide-en-US.pdf">SAML Client Adapter Userguide PDF</a></li>
<li><a href="rest-api/overview-index.html">Admin REST API</a></li>
<li><a href="javadocs/index.html">Javadocs</a></li>
</ul>
<table>
<tr>
<td>Server and Keycloak Adapter Userguide</td>
<td><a href="userguide/keycloak-server/html/index.html">HTML</a></td>
<td><a href="userguide/keycloak-server/html_single/index.html">HTML Single Page</a></td>
<td><a href="userguide/keycloak-server/pdf/keycloak-reference-guide-en-US.pdf">PDF</a></td>
</tr>
<tr>
<td>SAML Client Adapter Userguide</td>
<td><a href="userguide/saml-client-adapter/html/index.html">HTML</a></td>
<td><a href="userguide/saml-client-adapter/html_single/index.html">HTML Single Page</a></td>
<td><a href="userguide/saml-client-adapter/pdf/keycloak-saml-adapter-reference-guide-en-US.pdf">PDF</a></td>
</tr>
<tr>
<td>Admin REST API</td>
<td colspan="3"><a href="rest-api/index.html">HTML</a></td>
</tr>
<tr>
<td>Javadocs</td>
<td colspan="3"><a href="javadocs/index.html">HTML</a></td>
</tr>
</body>
</html>

View file

@ -66,4 +66,15 @@
</includes>
</fileSet>
</fileSets>
<files>
<file>
<source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
<outputDirectory>content/domain/servers/server-one/configuration</outputDirectory>
</file>
<file>
<source>src/main/resources/content/standalone/configuration/keycloak-server.json</source>
<outputDirectory>content/domain/servers/server-two/configuration</outputDirectory>
</file>
</files>
</assembly>

View file

@ -10,7 +10,7 @@
<subsystem>ee.xml</subsystem>
<subsystem>ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem>infinispan.xml</subsystem>
<subsystem>keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
@ -41,7 +41,7 @@
<subsystem>ee.xml</subsystem>
<subsystem supplement="ha">ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem supplement="ha">infinispan.xml</subsystem>
<subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
@ -74,7 +74,7 @@
<subsystem supplement="full">ee.xml</subsystem>
<subsystem supplement="full">ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem>infinispan.xml</subsystem>
<subsystem>keycloak-infinispan.xml</subsystem>
<subsystem>iiop-openjdk.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
@ -108,7 +108,7 @@
<subsystem supplement="full">ee.xml</subsystem>
<subsystem supplement="full-ha">ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem supplement="ha">infinispan.xml</subsystem>
<subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
<subsystem>iiop-openjdk.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>

View file

@ -79,16 +79,34 @@
<section>
<title>Version specific migration</title>
<section>
<title>Migrating to 1.7.0.CR1</title>
<simplesect>
<title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
<para>
In this version, we added <literal>First Broker Login</literal>, 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 <literal>First Login Flow</literal> to identity providers where
you can specify the flow and then you can configure this flow under <literal>Authentication</literal> tab in admin console.
</para>
<para>
We also removed the option <literal>Update Profile On First Login</literal> from the Identity provider settings and moved it
to the configuration of <literal>Review Profile</literal> authenticator. So once you specify which flow should be used for your
Identity provider (by default it's <literal>First Broker Login</literal> flow), you go to <literal>Authentication</literal> tab, select the flow
and then you configure the option under <literal>Review Profile</literal> authenticator.
</para>
</simplesect>
</section>
<section>
<title>Migrating to 1.6.0.Final</title>
<simplesect>
<title>Refresh tokens are not reusable anymore</title>
<title>Option that refresh tokens are not reusable anymore</title>
<para>
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 <literal>Revoke refresh token</literal> 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.
</para>
</simplesect>
<simplesect>

View file

@ -53,4 +53,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";
}

View file

@ -60,6 +60,8 @@ 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),

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>js-console</module-name>
</web-app>

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -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

View file

@ -126,4 +126,4 @@ locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais

View file

@ -150,4 +150,4 @@ locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais

View file

@ -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=&laquo; 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 <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> 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

View file

@ -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
@ -63,7 +63,7 @@ client_broker=Broker
requiredFields=Champs obligatoires
allFieldsRequired=Tous les champs obligatoires
allFieldsRequired=Tous les champs sont obligatoires
backToApplication=&laquo; 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 <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> 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.
@ -145,7 +145,7 @@ invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide : doit contenir au
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.
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.
@ -154,3 +154,4 @@ locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Fran\u00e7ais
locale_es=Espa\u00F1ol

View file

@ -125,4 +125,4 @@ locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais

View file

@ -147,4 +147,4 @@ locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (BR)
locale_fr=Français
locale_fr=Fran\u00e7ais

View file

@ -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.

View file

@ -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.
@ -374,6 +374,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 +394,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.
openid-connect-config=OpenID Connect Config
openid-connect-config.tooltip=OIDC SP and external IDP configuration.
authorization-url=Authorization URL

View file

@ -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}

View file

@ -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.

View file

@ -199,6 +199,9 @@ module.config([ '$routeProvider', function($routeProvider) {
},
providerFactory : function(IdentityProviderFactoryLoader) {
return {};
},
authFlows : function(AuthenticationFlowsLoader) {
return {};
}
},
controller : 'RealmIdentityProviderCtrl'
@ -217,6 +220,9 @@ module.config([ '$routeProvider', function($routeProvider) {
},
providerFactory : function(IdentityProviderFactoryLoader) {
return new IdentityProviderFactoryLoader();
},
authFlows : function(AuthenticationFlowsLoader) {
return AuthenticationFlowsLoader();
}
},
controller : 'RealmIdentityProviderCtrl'
@ -235,6 +241,9 @@ module.config([ '$routeProvider', function($routeProvider) {
},
providerFactory : function(IdentityProviderFactoryLoader) {
return IdentityProviderFactoryLoader();
},
authFlows : function(AuthenticationFlowsLoader) {
return AuthenticationFlowsLoader();
}
},
controller : 'RealmIdentityProviderCtrl'
@ -1406,12 +1415,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();
},
@ -1421,12 +1433,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();
},

View file

@ -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 ; i<authFlows.length ; i++) {
if (authFlows[i].providerId == 'basic-flow') {
$scope.authFlows.push(authFlows[i]);
}
}
$scope.$watch(function() {
return $location.path();
}, function() {
@ -1901,8 +1898,9 @@ module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredReq
});
module.controller('AuthenticationConfigCtrl', function($scope, realm, configType, config, AuthenticationConfig, Notifications, Dialog, $location) {
module.controller('AuthenticationConfigCtrl', function($scope, realm, flow, configType, config, AuthenticationConfig, Notifications, Dialog, $location) {
$scope.realm = realm;
$scope.flow = flow;
$scope.configType = configType;
$scope.create = false;
$scope.config = angular.copy(config);
@ -1927,7 +1925,7 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType
}, $scope.config, function() {
$scope.changed = false;
config = angular.copy($scope.config);
$location.url("/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + config.id);
$location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + config.id);
Notifications.success("Your changes have been saved.");
});
};
@ -1946,15 +1944,16 @@ module.controller('AuthenticationConfigCtrl', function($scope, realm, configType
Dialog.confirmDelete($scope.config.alias, 'config', function() {
AuthenticationConfig.remove({ realm: realm.realm, config : $scope.config.id }, function() {
Notifications.success("The config has been deleted.");
$location.url("/realms/" + realm.realm + '/authentication/flows');
$location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id);
});
});
};
});
module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) {
module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow, configType, execution, AuthenticationExecutionConfig, Notifications, Dialog, $location) {
$scope.realm = realm;
$scope.flow = flow;
$scope.create = true;
$scope.config = { config: {}};
$scope.configType = configType;
@ -1972,7 +1971,7 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, conf
}, $scope.config, function(data, headers) {
var l = headers().location;
var id = l.substring(l.lastIndexOf("/") + 1);
var url = "/realms/" + realm.realm + '/authentication/config/' + configType.providerId + "/" + id;
var url = "/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + id;
console.log('redirect url: ' + url);
$location.url(url);
Notifications.success("Config has been created.");

View file

@ -53,8 +53,8 @@
<li data-ng-hide="flow.builtIn"><a href="" ng-click="removeExecution(execution)">Delete</a></li>
<li data-ng-hide="flow.builtIn || !execution.authenticationFlow"><a href="" ng-click="addSubFlowExecution(execution)">Add Execution</a></li>
<li data-ng-hide="flow.builtIn || !execution.authenticationFlow"><a href="" ng-click="addSubFlow(execution)">Add Flow</a></li>
<li data-ng-show="execution.configurable && execution.authenticationConfig == null"><a href="#/create/authentication/{{realm.realm}}/execution/{{execution.id}}/provider/{{execution.providerId}}">Config</a></li>
<li data-ng-show="execution.configurable && execution.authenticationConfig != null"><a href="#/realms/{{realm.realm}}/authentication/config/{{execution.providerId}}/{{execution.authenticationConfig}}">Config</a></li>
<li data-ng-show="execution.configurable && execution.authenticationConfig == null"><a href="#/create/authentication/{{realm.realm}}/flows/{{flow.id}}/execution/{{execution.id}}/provider/{{execution.providerId}}">Config</a></li>
<li data-ng-show="execution.configurable && execution.authenticationConfig != null"><a href="#/realms/{{realm.realm}}/authentication/flows/{{flow.id}}/config/{{execution.providerId}}/{{execution.authenticationConfig}}">Config</a></li>
</ul>
</div>
</td>

View file

@ -2,6 +2,7 @@
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/authentication/flows">Authentication Flows</a></li>
<li><a href="#/realms/{{realm.realm}}/authentication/flows/{{flow.alias}}">{{flow.alias | capitalize}}</a></li>
<li class="active" data-ng-show="create">Create Authenticator Config</li>
<li class="active" data-ng-hide="create">{{config.alias}}</li>
</ol>

View file

@ -11,7 +11,7 @@
<div>
<select class="form-control" id="provider"
ng-model="provider"
ng-options="provider.id for provider in providers">
ng-options="provider.displayName|capitalize for provider in providers">
</select>
</div>
</div>

View file

@ -7,7 +7,7 @@
<div class="form-group">
<label class="col-md-2 control-label" for="alias">Alias </label>
<div class="col-sm-6">
<input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus>
<input class="form-control" type="text" id="alias" name="alias" data-ng-model="flow.alias" autofocus required>
</div>
<kc-tooltip>Specifies display name for the flow.</kc-tooltip>
</div>

View file

@ -46,7 +46,7 @@
<div class="form-group">
<label class="col-md-2 control-label" for="lookAhead">Look ahead window</label>
<div class="col-md-6">
<input class="form-control" type="text" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
<input class="form-control" type="number" required min="1" max="120" id="lookAhead" name="lookAhead" data-ng-model="realm.otpPolicyLookAheadWindow" autofocus>
</div>
<kc-tooltip>How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?</kc-tooltip>
</div>
@ -62,7 +62,7 @@
<div class="form-group" data-ng-show="realm.otpPolicyType == 'totp'">
<label class="col-md-2 control-label" for="counter">OTP Token Period</label>
<div class="col-md-6">
<input class="form-control" type="text" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
<input class="form-control" type="number" required min="1" max="120" id="period" name="period" data-ng-model="realm.otpPolicyPeriod">
</div>
<kc-tooltip>How many seconds should an OTP token be valid? Defaults to 30 seconds.</kc-tooltip>
</div>

View file

@ -1,4 +1,4 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" data-ng-init="initProvider()">
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
<li>{{identityProvider.alias}}</li>
@ -52,19 +52,6 @@
</div>
<kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
<div class="col-md-2">
<div>
<select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
<option value="on">{{:: 'on' | translate}}</option>
<option value="missing">{{:: 'on-missing-info' | translate}}</option>
<option value="off">{{:: 'off' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
<div class="col-md-6">
@ -79,6 +66,19 @@
</div>
<kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="firstBrokerLoginFlowAlias"
ng-model="identityProvider.firstBrokerLoginFlowAlias"
ng-options="flow.alias as flow.alias for flow in authFlows"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend uncollapsed><span class="text">{{:: 'openid-connect-config' | translate}}</span> <kc-tooltip>{{:: 'openid-connect-config.tooltip' | translate}}</kc-tooltip></legend>

View file

@ -52,19 +52,6 @@
</div>
<kc-tooltip>{{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
<div class="col-md-2">
<div>
<select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
<option value="on">{{:: 'on' | translate}}</option>
<option value="missing">{{:: 'on-missing-info' | translate}}</option>
<option value="off">{{:: 'off' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
<div class="col-md-6">
@ -79,6 +66,19 @@
</div>
<kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="firstBrokerLoginFlowAlias"
ng-model="identityProvider.firstBrokerLoginFlowAlias"
ng-options="flow.alias as flow.alias for flow in authFlows"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<fieldset>
<legend uncollapsed><span class="text">{{:: 'saml-config' | translate}}</span> <kc-tooltip>{{:: 'identity-provider.saml-config.tooltip' | translate}}</kc-tooltip></legend>

View file

@ -1,4 +1,4 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2" data-ng-init="initProvider()">
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/identity-provider-settings">{{:: 'identity-providers' | translate}}</a></li>
<li>{{identityProvider.alias}}</li>
@ -63,19 +63,6 @@
</div>
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="updateProfileFirstLoginMode">{{:: 'update-profile-on-first-login' | translate}}</label>
<div class="col-md-2">
<div>
<select id="updateProfileFirstLoginMode" ng-model="identityProvider.updateProfileFirstLoginMode" class="form-control">
<option value="on">{{:: 'on' | translate}}</option>
<option value="missing">{{:: 'on-missing-info' | translate}}</option>
<option value="off">{{:: 'off' | translate}}</option>
</select>
</div>
</div>
<kc-tooltip>{{:: 'update-profile-on-first-login.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="trustEmail">{{:: 'trust-email' | translate}}</label>
<div class="col-md-6">
@ -97,6 +84,19 @@
</div>
<kc-tooltip>{{:: 'gui-order.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="firstBrokerLoginFlowAlias">{{:: 'first-broker-login-flow' | translate}}</label>
<div class="col-md-6">
<div>
<select class="form-control" id="firstBrokerLoginFlowAlias"
ng-model="identityProvider.firstBrokerLoginFlowAlias"
ng-options="flow.alias as flow.alias for flow in authFlows"
required>
</select>
</div>
</div>
<kc-tooltip>{{:: 'first-broker-login-flow.tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group">

View file

@ -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">
<div id="kc-error-message">
<p class="instruction">${message.summary}</p>
</div>
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
</div>
</form>
</#if>
</@layout.registrationLayout>

View file

@ -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">
<p class="instruction">
${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
</p>
<p class="instruction">
${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
</p>
</#if>
</@layout.registrationLayout>

View file

@ -6,7 +6,7 @@
${msg("loginProfileTitle")}
<#elseif section = "form">
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<#if realm.editUsernameAllowed>
<#if user.editUsernameAllowed>
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>

View file

@ -13,7 +13,11 @@
</div>
<div class="${properties.kcInputWrapperClass!}">
<#if usernameEditDisabled??>
<input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" disabled />
<#else>
<input id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')?html}" type="text" autofocus />
</#if>
</div>
</div>
@ -29,7 +33,7 @@
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<#if realm.rememberMe>
<#if realm.rememberMe && !usernameEditDisabled??>
<div class="checkbox">
<label>
<#if login.rememberMe??>
@ -56,7 +60,7 @@
</form>
</#if>
<#elseif section = "info" >
<#if realm.password && realm.registrationAllowed>
<#if realm.password && realm.registrationAllowed && !usernameEditDisabled??>
<div id="kc-registration">
<span>${msg("noAccount")} <a href="${url.registrationUrl}">${msg("doRegister")}</a></span>
</div>

View file

@ -197,4 +197,5 @@ locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais
locale_es=Espa\u00F1ol

View file

@ -79,6 +79,11 @@ emailVerifyInstruction1=An email with instructions to verify your email address
emailVerifyInstruction2=Haven''t received a verification code in your email?
emailVerifyInstruction3=to re-send the email.
emailLinkIdpTitle=Link {0}
emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you.
emailLinkIdp2=Haven''t received a verification code in your email?
emailLinkIdp3=to re-send the email.
backToLogin=&laquo; Back to Login
emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
@ -132,13 +137,19 @@ invalidTotpMessage=Invalid authenticator code.
usernameExistsMessage=Username already exists.
emailExistsMessage=Email already exists.
federatedIdentityEmailExistsMessage=User with email already exists. Please login to account management to link the account.
federatedIdentityUsernameExistsMessage=User with username already exists. Please login to account management to link the account.
federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account.
confirmLinkIdpTitle=Account already exists
federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue?
federatedIdentityConfirmReauthenticateMessage=Authenticate as {0} to link your account with {1}
confirmLinkIdpReviewProfile=Review profile info
confirmLinkIdpContinue=Link {0} with existing account
configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
updateProfileMessage=You need to update your user profile to activate your account.
updatePasswordMessage=You need to change your password to activate your account.
verifyEmailMessage=You need to verify your email address to activate your account.
linkIdpMessage=You need to verify your email address to link your account with {0}.
emailSentMessage=You should receive an email shortly with further instructions.
emailSendErrorMessage=Failed to send email, please try again later.
@ -181,6 +192,7 @@ couldNotObtainTokenMessage=Could not obtain token from identity provider.
unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider.
unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider.
identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider.
identityProviderDifferentUserMessage=Authenticated as {0}, but expected to be authenticated as {1}
couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider.
unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider.
invalidAccessCodeMessage=Invalid access code.
@ -188,6 +200,7 @@ sessionNotActiveMessage=Session not active.
invalidCodeMessage=An error occurred, please login again through your application.
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
identityProviderLinkSuccess=Your account was successfully linked with {0} account {1} .
realmSupportsNoCredentialsMessage=Realm does not support any credential type.
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
emailVerifiedMessage=Your email address has been verified.
@ -196,7 +209,8 @@ locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais
locale_es=Espa\u00F1ol
backToApplication=&laquo; Back to Application
missingParameterMessage=Missing parameters\: {0}

View file

@ -0,0 +1,205 @@
doLogIn=Iniciar sesi\u00F3n
doRegister=Reg\u00EDstrate
doCancel=Cancelar
doSubmit=Enviar
doYes=S\u00ED
doNo=No
doContinue=Continuar
doAccept=Aceptar
doDecline=Declinar
doForgotPassword=\u00BFHas olvidado tu contrase\u00F1a?
doClickHere=Haz clic aqu\u00ED
doImpersonate=Personificar
kerberosNotConfigured=Kerberos no configurado
kerberosNotConfiguredTitle=Kerberos no configurado
bypassKerberosDetail=O bien no est\u00E1s identificado mediante Kerberos o tu navegador no est\u00E1 configurado para identificarse mediante Kerberos. Por favor haz clic para identificarte por otro medio.
kerberosNotSetUp=Kerberos no est\u00E1 configurado. No puedes identificarte.
registerWithTitle=Reg\u00EDstrate con {0}
registerWithTitleHtml=Reg\u00EDstrate con <strong>{0}</strong>
loginTitle=Inicia sesi\u00F3n en {0}
loginTitleHtml=Inicia sesi\u00F3n en {0}
impersonateTitle={0}\u00A0Personificar Usuario
impersonateTitleHtml=<strong>{0}</strong> Personificar Usuario</strong>
realmChoice=Dominio
unknownUser=Usuario desconocido
loginTotpTitle=Configura tu aplicaci\u00F3n de identificaci\u00F3n m\u00F3vil
loginProfileTitle=Actualiza la informaci\u00F3n de tu cuenta
loginTimeout=Has tardado demasiado en identificarte. Inicia de nuevo la identificaci\u00F3n.
oauthGrantTitle=Concesi\u00F3n OAuth
oauthGrantTitleHtml=Acceso temporal para <strong>{0}</strong> solicitado por
errorTitle=Lo sentimos...
errorTitleHtml=Lo <strong>sentimos</strong>...
emailVerifyTitle=Verificaci\u00F3n del email
emailForgotTitle=\u00BFHas olvidado tu contrase\u00F1a?
updatePasswordTitle=Modificaci\u00F3n de contrase\u00F1a
codeSuccessTitle=C\u00F3digo de \u00E9xito
codeErrorTitle=C\u00F3digo de error: {0}
termsTitle=T\u00E9rminos y Condiciones
termsTitleHtml=T\u00E9rminos y Condiciones
termsText=<p>T\u00E9rmines y condiciones a definir</p>
recaptchaFailed=Reconocimiento de texto inv\u00E1lido
recaptchaNotConfigured=El reconocimiento de texto es obligatorio pero no est\u00E1 configurado
consentDenied=Consentimiento rechazado.
noAccount=\u00BFUsuario nuevo?
username=Usuario
usernameOrEmail=Usuario o email
firstName=Nombre
givenName=Nombre de pila
fullName=Nombre completo
lastName=Apellidos
familyName=Apellidos
email=Email
password=Contrase\u00F1a
passwordConfirm=Confirma la contrase\u00F1a
passwordNew=Nueva contrase\u00F1a
passwordNewConfirm=Confirma la nueva contrase\u00F1a
rememberMe=Seguir conectado
authenticatorCode=C\u00F3digo de identificaci\u00F3n
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
loginTotpStep1=Instala <a href=\"https://fedorahosted.org/freeotp/\" target=\"_blank\">FreeOTP</a> o Google Authenticator en tu tel\u00E9fono m\u00F3vil. Ambas aplicaciones est\u00E1n disponibles en <a href=\"https://play.google.com\">Google Play</a> y en la App Store de Apple.
loginTotpStep2=Abre la aplicacvi\u00F3n y escanea el c\u00F3digo o introduce la clave.
loginTotpStep3=Introduce el c\u00F3digo \u00FAnico que te muestra la aplicaci\u00F3n de autenticaci\u00F3n y haz clic en Enviar para finalizar la configuraci\u00F3n
loginTotpOneTime=C\u00F3digo de un solo uso
oauthGrantRequest=\u00BFQuieres permitir estos privilegios de acceso?
inResource=en
emailVerifyInstruction1=Te hemos enviado un email con instrucciones para verificar tu email.
emailVerifyInstruction2=\u00BFNo has recibido un c\u00F3digo de verificaci\u00F3n en tu email?
emailVerifyInstruction3=para reenviar el email.
backToLogin=&laquo; Volver a la identificaci\u00F3n
emailInstruction=Indica tu usuario o email y te enviaremos instruciones indicando como generar una nueva contrase\u00F1a.
copyCodeInstruction=Por favor, copia y pega este c\u00F3digo en tu aplicaci\u00F3n:
personalInfo=Informaci\u00F3n personal:
role_admin=Admin
role_realm-admin=Administrador del dominio
role_create-realm=Crear dominio
role_create-client=Crear cliente
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 del dominio
client_broker=Broker
invalidUserMessage=Usuario o contrase\u00F1a incorrectos.
invalidEmailMessage=Email no v\u00E1lido
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.
expiredCodeMessage=Se agot\u00F3 el tiempo m\u00E1ximo para la identificaci\u00F3n. Por favor identificate de nuevo.
missingFirstNameMessage=Por favor indica tu nombre.
missingLastNameMessage=Por favor indica tus apellidos.
missingEmailMessage=Por favor indica tu email.
missingUsernameMessage=Por favor indica tu usuario.
missingPasswordMessage=Por favor indica tu contrase\u00F1a.
missingTotpMessage=Por favor indica tu c\u00F3digo de autenticaci\u00F3n
notMatchPasswordMessage=Las contrase\u00F1as no coinciden.
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 nombre de usuario ya existe
emailExistsMessage=El email ya existe
federatedIdentityEmailExistsMessage=Ya existe un usuario con este email. Por favor accede a la gesti\u00F3n de tu cuenta para enlazarlo.
federatedIdentityUsernameExistsMessage=Ya existe un usuario con este nombre de usuario. Por favor accede a la gesti\u00F3n de tu cuenta para enlazarlo.
configureTotpMessage=Tienes que configurar la aplicaci\u00F3n m\u00F3vil de identificaci\u00F3n para activar tu cuenta.
updateProfileMessage=Tienes que actualizar tu perfil de usuario para activar tu cuenta.
updatePasswordMessage=Tienes que cambiar tu contrase\u00F1a para activar tu cuenta.
verifyEmailMessage=Tienes que verificar tu email para activar tu cuenta.
emailSentMessage=En breve deber\u00EDas recibir un mensaje con m\u00E1s instrucciones
emailSendErrorMessage=Fall\u00F3 el env\u00EDo del email, por favor int\u00E9ntalo de nuevo m\u00E1s tarde.
accountUpdatedMessage=Tu cuenta se ha actualizado.
accountPasswordUpdatedMessage=Tu contrase\u00F1a se ha actualizado.
noAccessMessage=Sin acceso
invalidPasswordMinLengthMessage=Contrase\u00F1a incorrecta: longitud m\u00EDnima {0}.
invalidPasswordMinDigitsMessage=Contrase\u00F1a incorrecta: debe contaner al menos {0} caracteres num\u00E9ricos.
invalidPasswordMinLowerCaseCharsMessage=Contrase\u00F1a incorrecta: debe contener al menos {0} letras min\u00FAsculas.
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.
failedToProcessResponseMessage=Fallo al procesar la respuesta
httpsRequiredMessage=HTTPS obligatorio
realmNotEnabledMessage=El dominio no est\u00E1 activado
invalidRequestMessage=Petici\u00F3n incorrecta
failedLogout=Fall\u00F3 la desconexi\u00F3n.
unknownLoginRequesterMessage=Solicitante de identificaci\u00F3n desconocido
loginRequesterNotEnabledMessage=El solicitante de inicio de sesi\u00F3n est\u00E1 desactivado
bearerOnlyMessage=Las aplicaciones Bearer-only no pueden iniciar sesi\u00F3n desde el navegador.
directGrantsOnlyMessage=Los clientes de tipo Direct-grants-only no pueden iniciar sesi\u00F3n desde el navegador.
invalidRedirectUriMessage=La URI de redirecci\u00F3n no es correcta
unsupportedNameIdFormatMessage=NameIDFormat no soportado
invlidRequesterMessage=Solicitante no v\u00E1lido
registrationNotAllowedMessage=El registro no est\u00E1 permitido
resetCredentialNotAllowedMessage=El renicio de las credenciales no est\u00E1 permitido
permissionNotApprovedMessage=Permiso no aprobado.
noRelayStateInResponseMessage=Sin estado de retransmisi\u00F3n en la respuesta del proveedor de identidad.
identityProviderAlreadyLinkedMessage=La identidad devuelta por el proveedor de identidad ya est\u00E1 asociada a otro usuario.
insufficientPermissionMessage=Permisos insuficientes para enlazar identidades.
couldNotProceedWithAuthenticationRequestMessage=No se pudo continuar con la petici\u00F3n de autenticaci\u00F3n al proveedor de identidad.
couldNotObtainTokenMessage=.No se pudo obtener el c\u00F3digo del proveedor de identidad
unexpectedErrorRetrievingTokenMessage=Error inesperado obteniendo el token del proveedor de identidad
unexpectedErrorHandlingResponseMessage=Error inesperado procesando la respuesta del proveedor de identidad.
identityProviderAuthenticationFailedMessage=Fall\u00F3 la autenticaci\u00F3n. No fue posible autenticarse en el proveedor de identidad.
couldNotSendAuthenticationRequestMessage=No se pudo enviar la petici\u00F3n de identificaci\u00F3n al proveedor de identidad.
unexpectedErrorHandlingRequestMessage=Error inesperado durante la petici\u00F3n de identificaci\u00F3n al proveedor de identidad.
invalidAccessCodeMessage=C\u00F3digo de acceso no v\u00E1lido.
sessionNotActiveMessage=La sesi\u00F3n no est\u00E1 activa
invalidCodeMessage=Ha ocurrido un error, por favor identificate de nuevo desde tu aplicaci\u00F3n.
identityProviderUnexpectedErrorMessage=Error no esperado intentado autenticar en el proveedor de identidad.
identityProviderNotFoundMessage=No se encontr\u00F3 un proveedor de identidad.
realmSupportsNoCredentialsMessage=El dominio no soporta ning\u00FAn tipo de credenciales.
identityProviderNotUniqueMessage=El dominio soporta m\u00FAltiples proveedores de identidad. No se pudo determinar el proveedor de identidad que deber\u00EDa ser utilizado para identificarse.
emailVerifiedMessage=Tu email ha sido verificado.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Fran\u00C3\u00A7ais
locale_es=Espa\u00F1ol
backToApplication=&laquo; Volver a la aplicaci\u00F3n
missingParameterMessage=Par\u00E1metros que faltan: {0}
clientNotFoundMessage=Cliente no encontrado
invalidParameterMessage=Par\u00E1metro no v\u00E1lido: {0}

View file

@ -6,48 +6,48 @@ doYes=Oui
doNo=Non
doContinue=Continuer
doAccept=Accepter
doDecline=Decliner
doForgotPassword=Mot de passe oublié ?
doDecline=D\u00e9cliner
doForgotPassword=Mot de passe oubli\u00e9 ?
doClickHere=Cliquez ici
doImpersonate=Impersonate
kerberosNotConfigured=Kerberos non configuré
kerberosNotConfiguredTitle=Kerberos non configuré
bypassKerberosDetail=Si vous n''êtes pas connecté via Kerberos ou bie que votre navigateur n''est opas configurer pour la connexion via Kerberos. Veuillez cliquer pour vous connecter via un autre moyen.
kerberosNotSetUp=Kerberos n''est pas configuré. Connexion impossible.
kerberosNotConfigured=Kerberos non configur\u00e9
kerberosNotConfiguredTitle=Kerberos non configur\u00e9
bypassKerberosDetail=Si vous n''\u00eates pas connect\u00e9 via Kerberos ou bien que votre navigateur n''est pas configur\u00e9 pour la connexion via Kerberos. Veuillez cliquer pour vous connecter via un autre moyen.
kerberosNotSetUp=Kerberos n''est pas configur\u00e9. Connexion impossible.
registerWithTitle=Enregistrement avec {0}
registerWithTitleHtml=Enregistrement avec <strong>{0}</strong>
loginTitle=Connecté {0}
loginTitleHtml=Connecté <strong>{0}</strong>
loginTitle=Se connecter \u00e0 {0}
loginTitleHtml=Se connecter \u00e0 <strong>{0}</strong>
impersonateTitle={0} utilisateur impersonate
impersonateTitleHtml=<strong>{0}</strong> utilisateur impersonate</strong>
realmChoice=Domaine
unknownUser=Utilisateur inconnu
loginTotpTitle=Configuration de l''authentification par mobile
loginProfileTitle=Mise à jour du compte
loginTimeout=Le temps imparti pour la connexion est écoulé. Le processus de connexion redémarre depuis le debut.
loginProfileTitle=Mise \u00e0 jour du compte
loginTimeout=Le temps imparti pour la connexion est \u00e9coul\u00e9. Le processus de connexion red\u00e9marre depuis le d\u00e9but.
oauthGrantTitle=OAuth Grant
oauthGrantTitleHtml=Accès temporaire pour <strong>{0}</strong> demandé par
errorTitle=Nous sommes désolé ...
errorTitleHtml=Nous sommes <strong>désolé</strong> ...
emailVerifyTitle=Vérification du courriel
emailForgotTitle=Mot de passe oublié ?
updatePasswordTitle=Mise à jour du mot de passe
codeSuccessTitle=Code succès
codeErrorTitle=Code Erreur\: {0}
oauthGrantTitleHtml=Acc\u00e8s temporaire pour <strong>{0}</strong> demand\u00e9 par
errorTitle=Nous sommes d\u00e9sol\u00e9 ...
errorTitleHtml=Nous sommes <strong>d\u00e9sol\u00e9</strong> ...
emailVerifyTitle=V\u00e9rification du courriel
emailForgotTitle=Mot de passe oubli\u00e9 ?
updatePasswordTitle=Mise \u00e0 jour du mot de passe
codeSuccessTitle=Code succ\u00e8s
codeErrorTitle=Code d''erreur\: {0}
termsTitle=Termes et Conditions
termsTitleHtml=Termes et Conditions
termsText=<p>Termes et conditions à définir</p>
termsText=<p>Termes et conditions \u00e0 d\u00e9finir</p>
recaptchaFailed=Re-captcha invalide
recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configuré
consentDenied=Consentement refusé.
recaptchaNotConfigured=Re-captcha est requis, mais il n''est pas configur\u00e9
consentDenied=Consentement refus\u00e9.
noAccount=Nouvel utilisateur?
username=Nom d''utilisateur
usernameOrEmail=Nom d''utilisateur ou courriel
firstName=Prénom
givenName=Prénom
firstName=Pr\u00e9nom
givenName=Pr\u00e9nom
fullName=Nom complet
lastName=Nom
familyName=Nom de famille
@ -55,38 +55,38 @@ email=Courriel
password=Mot de passe
passwordConfirm=Confirmation du mot de passe
passwordNew=Nouveau mot de passe
passwordNewConfirm=Confirmation du nouveau not de passe
passwordNewConfirm=Confirmation du nouveau mot de passe
rememberMe=Se souvenir de moi
authenticatorCode=Code à usage unique
authenticatorCode=Code \u00e0 usage unique
address=Adresse
street=Rue
locality=Ville ou Localité
region=État, Province, ou Région
locality=Ville ou Localit\u00e9
region=\u00c9tat, Province ou R\u00e9gion
postal_code=Code postal
country=Pays
emailVerified=Courriel vérifié
gssDelegationCredential=GSS Delegation Credential
emailVerified=Courriel v\u00e9rifi\u00e9
gssDelegationCredential=Accr\u00e9ditation de d\u00e9l\u00e9gation GSS
loginTotpStep1=Installez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
loginTotpStep2=Ouvrez l''application et scanner le code bar ou entrez la clef.
loginTotpStep2=Ouvrez l''application et scanner le code barre ou entrez la cl\u00e9.
loginTotpStep3=Entrez le code \u00e0 usage unique fourni par l''application et cliquez sur Sauvegarder pour terminer.
loginTotpOneTime=Code à usage unique
loginTotpOneTime=Code \u00e0 usage unique
oauthGrantRequest=Voulez-vous accorder ces privileges d''accès ?
oauthGrantRequest=Voulez-vous accorder ces privil\u00e8ges d''acc\u00e8s ?
inResource=dans
emailVerifyInstruction1=Un courriel avec des instructions à suivre vous a été envoyé.
emailVerifyInstruction2=Vous n''avez pas reçu de code dans le courriel ?
emailVerifyInstruction1=Un courriel avec des instructions \u00e0 suivre vous a \u00e9t\u00e9 envoy\u00e9.
emailVerifyInstruction2=Vous n''avez pas re\u00e7u de code dans le courriel?
emailVerifyInstruction3=Pour renvoyer le courriel.
backToLogin=&laquo; Retour à la connexion
backToLogin=&laquo; Retour \u00e0 la connexion
emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un email va vous \u00eatre envoyer vous permettant de créer un nouveau mot de passe.
emailInstruction=Entrez votre nom d''utilisateur ou votre courriel, un courriel va vous \u00eatre envoy\u00e9 vous permettant de cr\u00e9er un nouveau mot de passe.
copyCodeInstruction=Copiez le code et recopiez le dans votre application:
personalInfo=Information personnelle:
role_admin=Adminitrateur
role_admin=Administrateur
role_realm-admin=Administrateur du domaine
role_create-realm=Cr\u00e9er un domaine
role_view-realm=Voir un domaine
@ -111,9 +111,9 @@ client_realm-management=Gestion du domaine
client_broker=Broker
invalidUserMessage=Nom d''utilisateur ou mot de passe invalide.
invalidEmailMessage=Adresse courriel invalide.
accountDisabledMessage=Compte désactivé, contactez votre administrateur.
accountTemporarilyDisabledMessage=Ce compte est temporairement désactivé, contactez votre administrateur ou bien réassayer plus tard.
invalidEmailMessage=Courriel invalide.
accountDisabledMessage=Compte d\u00e9sactiv\u00e9, contactez votre administrateur.
accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, contactez votre administrateur ou bien r\u00e9assayer plus tard.
expiredCodeMessage=Fin de connexion. Veuillez vous reconnecter.
missingFirstNameMessage=Veuillez entrer votre pr\u00e9nom.
@ -131,75 +131,76 @@ invalidTotpMessage=Le code d''authentification est invalide.
usernameExistsMessage=Le nom d''utilisateur existe d\u00e9j\u00e0.
emailExistsMessage=Le courriel existe d\u00e9j\u00e0.
federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe déjà. Veuillez vous connecté au gestionnaire de compte pour lier le compte.
federatedIdentityEmailExistsMessage=Cet utilisateur avec ce courriel existe d\u00e9j\u00e0. Veuillez vous connect\u00e9 au gestionnaire de compte pour lier le compte.
federatedIdentityUsernameExistsMessage=Cet utilisateur avec ce nom d''utilisateur existe d\u00e9j\u00e0. Veuillez vous connect\u00e9 au gestionnaire de compte pour lier le compte.
configureTotpMessage=Vous devez configurer l''authentification par mobile pour activer votre compte.
updateProfileMessage=Vous devez mettre à jour votre profile pour activer votre compte.
updateProfileMessage=Vous devez mettre \u00e0 jour votre profile pour activer votre compte.
updatePasswordMessage=Vous devez changer votre mot de passe pour activer votre compte.
verifyEmailMessage=Vous devez vérifier votre courriel pour activer votre compte.
verifyEmailMessage=Vous devez v\u00e9rifier votre courriel pour activer votre compte.
emailSentMessage=Vous devriez recevoir rapidement un courriel avec de plus ample instructions.
emailSendErrorMessage=Erreur lors de l''envoie du courriel, veuillez essayer plus tard.
accountUpdatedMessage=Votre compte a été mis à jour.
accountPasswordUpdatedMessage=Votre mot de passe a été mis à jour.
accountUpdatedMessage=Votre compte a \u00e9t\u00e9 mis \u00e0 jour.
accountPasswordUpdatedMessage=Votre mot de passe a \u00e9t\u00e9 mis \u00e0 jour.
noAccessMessage=Aucun accès
noAccessMessage=Aucun acc\u00e8s
invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
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.
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.
failedToProcessResponseMessage=Erreur lors du traitement de la réponse
failedToProcessResponseMessage=Erreur lors du traitement de la r\u00e9ponse
httpsRequiredMessage=Le protocole HTTPS est requis
realmNotEnabledMessage=Le domaine n''est pas activé
invalidRequestMessage=Requete invalide
failedLogout=La déconnexion a échouée
realmNotEnabledMessage=Le domaine n''est pas activ\u00e9
invalidRequestMessage=Requ\u00eate invalide
failedLogout=La d\u00e9connexion a \u00e9chou\u00e9e
unknownLoginRequesterMessage=Compte inconnu du demandeur
loginRequesterNotEnabledMessage=La connexion du demandeur n''est pas active
bearerOnlyMessage=Les applications Bearer-only ne sont pas autorisées à initier la connexion par navigateur.
directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autorisés à initier la connexion par navigateur.
bearerOnlyMessage=Les applications Bearer-only ne sont pas autoris\u00e9es \u00e0 initier la connexion par navigateur.
directGrantsOnlyMessage=Les clients Direct-grants-only ne sont pas autoris\u00e9s \u00e0 initier la connexion par navigateur.
invalidRedirectUriMessage=L''uri de redirection est invalide
unsupportedNameIdFormatMessage=NameIDFormat non supporté
unsupportedNameIdFormatMessage=NameIDFormat non support\u00e9
invlidRequesterMessage=Demandeur invalide
registrationNotAllowedMessage=L''enregistrement n''est pas autorisé
resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autorisé
registrationNotAllowedMessage=L''enregistrement n''est pas autoris\u00e9
resetCredentialNotAllowedMessage=La remise \u00e0 z\u00e9ro n''est pas autoris\u00e9
permissionNotApprovedMessage=La permission n''est pas approuvée.
noRelayStateInResponseMessage=Aucun état de relais dans la réponse du fournisseur d''identité.
identityProviderAlreadyLinkedMessage=L''identité retournée par le fournisseur d''identité est déjà liée avec un autre utilisateur.
insufficientPermissionMessage=Permissions insuffisantes pour lier les identités.
couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requête d''authentification vers le fournisseur d''identité.
couldNotObtainTokenMessage=Impossible de récuperer le jeton du fournisseur d''identité.
unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la récupération du jeton provenant du fournisseur d''identité.
unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la réponse provenant du fournisseur d''identité.
identityProviderAuthenticationFailedMessage=L''authentification a échouée. Impossible de s''authentifier avec le fournisseur d''identité.
couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requête d''authentification vers le fournisseur d''identité.
unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requête vers le fournisseur d''identité.
invalidAccessCodeMessage=Code d''accès invalide.
permissionNotApprovedMessage=La permission n''est pas approuv\u00e9e.
noRelayStateInResponseMessage=Aucun \u00e9tat de relais dans la r\u00e9ponse du fournisseur d''identit\u00e9.
identityProviderAlreadyLinkedMessage=L''identit\u00e9 retourn\u00e9e par le fournisseur d''identit\u00e9 est d\u00e9j\u00e0 li\u00e9e avec un autre utilisateur.
insufficientPermissionMessage=Permissions insuffisantes pour lier les identit\u00e9s.
couldNotProceedWithAuthenticationRequestMessage=Impossible de continuer avec la requ\u00eate d''authentification vers le fournisseur d''identit\u00e9.
couldNotObtainTokenMessage=Impossible de r\u00e9cup\u00e9rer le jeton du fournisseur d''identit\u00e9.
unexpectedErrorRetrievingTokenMessage=Erreur inattendue lors de la r\u00e9cup\u00e9ration du jeton provenant du fournisseur d''identit\u00e9.
unexpectedErrorHandlingResponseMessage=Erreur inattendue lors du traitement de la r\u00e9ponse provenant du fournisseur d''identit\u00e9.
identityProviderAuthenticationFailedMessage=L''authentification a \u00e9chou\u00e9e. Impossible de s''authentifier avec le fournisseur d''identit\u00e9.
couldNotSendAuthenticationRequestMessage=Impossible d''envoyer la requ\u00eate d''authentification vers le fournisseur d''identit\u00e9.
unexpectedErrorHandlingRequestMessage=Erreur inattendue lors du traitement de la requ\u00eate vers le fournisseur d''identit\u00e9.
invalidAccessCodeMessage=Code d''acc\u00e8s invalide.
sessionNotActiveMessage=La session n''est pas active.
invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter à votre application.
identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identité.
identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identité avec ce identifiant.
realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accréditation.
identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identité. Impossible de déterminer fournisseurs d''identité avec lequel s''authentifier.
emailVerifiedMessage=Votre adresse courriel a été vérifiée.
invalidCodeMessage=Une erreur est survenue, veuillez vous reconnecter \u00e0 votre application.
identityProviderUnexpectedErrorMessage=Erreur inattendue lors de l''authentification avec fournisseur d''identit\u00e9.
identityProviderNotFoundMessage=Impossible de trouver le fournisseur d''identit\u00e9 avec cet identifiant.
realmSupportsNoCredentialsMessage=Ce domaine ne supporte aucun type d''accr\u00e9ditation.
identityProviderNotUniqueMessage=Ce domaine autorise plusieurs fournisseurs d''identit\u00e9. Impossible de d\u00e9terminer le fournisseur d''identit\u00e9 avec lequel s''authentifier.
emailVerifiedMessage=Votre courriel a \u00e9t\u00e9 v\u00e9rifi\u00e9.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais
locale_es=Espa\u00F1ol
backToApplication=&laquo; Revenir à l''application
missingParameterMessage=Paramètres manquants \: {0}
backToApplication=&laquo; Revenir \u00e0 l''application
missingParameterMessage=Param\u00e8tres manquants\: {0}
clientNotFoundMessage=Client inconnu.
invalidParameterMessage=Paramètres invalide \: {0}
invalidParameterMessage=Param\u00e8tre invalide\: {0}

View file

@ -189,7 +189,8 @@ locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français
locale_fr=Fran\u00e7ais
locale_es=Espa\u00F1ol
backToApplication=&laquo; Torna all''Applicazione
missingParameterMessage=Parametri Mancanti\: {0}

View file

@ -194,7 +194,8 @@ locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (BR)
locale_fr=Français
locale_fr=Fran\u00e7ais
locale_es=Espa\u00F1ol
backToApplication=&laquo; Voltar para o aplicativo
missingParameterMessage=Par\u00E2metros que faltam\: {0}

View file

@ -0,0 +1,5 @@
<html>
<body>
${msg("identityProviderLinkBodyHtml", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration)}
</body>
</html>

View file

@ -1,6 +1,9 @@
emailVerificationSubject=Verify email
emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn''t create this account, just ignore this message.
emailVerificationBodyHtml=<p>Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you didn''t create this account, just ignore this message.</p>
identityProviderLinkSubject=Link {0}
identityProviderLinkBody=Someone wants to link your "{1}" account with "{0}" account of user {2} . If this was you, click the link below to link accounts\n\n{3}\n\nThis link will expire within {4} minutes.\n\nIf you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.
identityProviderLinkBodyHtml=<p>Someone wants to link your <b>{1}</b> account with <b>{0}</b> account of user {2} . If this was you, click the link below to link accounts</p><p><a href="{3}">{3}</a></p><p>This link will expire within {4} minutes.</p><p>If you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.</p>
passwordResetSubject=Reset password
passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>

View file

@ -0,0 +1,21 @@
emailVerificationSubject=Verificaci\u00F3n de email
emailVerificationBody=Alguien ha creado una cuenta de {2} con esta direcci\u00F3n de email. Si has sido t\u00FA, haz click en el enlace siguiente para verificar tu direcci\u00F3n de email.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutos.\n\nSi t\u00FA no has creado esta cuenta, simplemente ignora este mensaje.
emailVerificationBodyHtml=<p>Alguien ha creado una cuenta de {2} con esta direcci\u00F3n de email. Si has sido t\u00FA, haz click en el enlace siguiente para verificar tu direcci\u00F3n de email.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutos.</p><p>Si t\u00FA no has creado esta cuenta, simplemente ignora este mensaje.</p>
passwordResetSubject=Reiniciar contrase\u00F1a
passwordResetBody=Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido t\u00FA, haz clic en el enlace siguiente para reiniciarlas.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutos.\n\nSi no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.
passwordResetBodyHtml=<p>Alguien ha solicitado cambiar las credenciales de tu cuenta de {2}. Si has sido t\u00FA, haz clic en el enlace siguiente para reiniciarlas.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutos.</p><p>Si no quieres reiniciar tus credenciales, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.</p>
executeActionsSubject=Actualiza tu cuenta
executeActionsBody=El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.\n\n{0}\n\nEste enlace expirar\u00E1 en {1} minutes.\n\nSi no est\u00E1s al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.
executeActionsBodyHtml=<p>El administrador ha solicitado que actualices tu cuenta de {2}. Haz clic en el enlace inferior para iniciar este proceso.</p><p><a href=\"{0}\">{0}</a></p><p>Este enlace expirar\u00E1 en {1} minutes.</p><p>Si no est\u00E1s al tanto de que el administrador haya solicitado esto, simplemente ignora este mensaje y no se realizar\u00E1 ning\u00FAn cambio.</p>
eventLoginErrorSubject=Fallo en el inicio de sesi\u00F3n
eventLoginErrorBody=Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
eventLoginErrorBodyHtml=<p>Se ha detectado un intento de acceso fallido a tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
eventRemoveTotpSubject=Borrado TOTP
eventRemoveTotpBody=TOTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
eventRemoveTotpBodyHtml=<p>TOTP fue eliminado de tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
eventUpdatePasswordSubject=Actualizaci\u00F3n de contrase\u00F1a
eventUpdatePasswordBody=Tu contrase\u00F1a se ha actualizado el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
eventUpdatePasswordBodyHtml=<p>Tu contrase\u00F1a se ha actualizado el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>
eventUpdateTotpSubject=Actualizaci\u00F3n de TOTP
eventUpdateTotpBody=TOTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.
eventUpdateTotpBodyHtml=<p>TOTP se ha actualizado en tu cuenta el {0} desde {1}. Si no has sido t\u00FA, por favor contacta con el administrador.</p>

View file

@ -0,0 +1 @@
${msg("identityProviderLinkBody", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration)}

View file

@ -10,10 +10,14 @@ import org.keycloak.provider.Provider;
*/
public interface EmailProvider extends Provider {
String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
public EmailProvider setRealm(RealmModel realm);
public EmailProvider setUser(UserModel user);
public EmailProvider setAttribute(String name, Object value);
public void sendEvent(Event event) throws EmailException;
/**
@ -25,6 +29,11 @@ public interface EmailProvider extends Provider {
*/
public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException;
/**
* Send to confirm that user wants to link his account with identity broker link
*/
void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException;
/**
* Change password email requested by admin
*

View file

@ -1,8 +1,11 @@
package org.keycloak.email.freemarker;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@ -17,6 +20,7 @@ import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.jboss.logging.Logger;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.email.freemarker.beans.EventBean;
@ -28,6 +32,7 @@ import org.keycloak.freemarker.FreeMarkerUtil;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.beans.MessageFormatterMethod;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -43,6 +48,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
private FreeMarkerUtil freeMarker;
private RealmModel realm;
private UserModel user;
private final Map<String, Object> attributes = new HashMap<String, Object>();
public FreeMarkerEmailProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session;
@ -61,6 +67,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
return this;
}
@Override
public EmailProvider setAttribute(String name, Object value) {
attributes.put(name, value);
return this;
}
@Override
public void sendEvent(Event event) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
@ -83,6 +95,27 @@ public class FreeMarkerEmailProvider implements EmailProvider {
send("passwordResetSubject", "password-reset.ftl", attributes);
}
@Override
public void sendConfirmIdentityBrokerLink(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("user", new ProfileBean(user));
attributes.put("link", link);
attributes.put("linkExpiration", expirationInMinutes);
String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
attributes.put("realmName", realmName);
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
String idpAlias = brokerContext.getIdpConfig().getAlias();
idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
attributes.put("identityProviderContext", brokerContext);
attributes.put("identityProviderAlias", idpAlias);
List<Object> subjectAttrs = Arrays.<Object>asList(idpAlias);
send("identityProviderLinkSubject", subjectAttrs, "identity-provider-link.ftl", attributes);
}
@Override
public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
@ -111,6 +144,10 @@ public class FreeMarkerEmailProvider implements EmailProvider {
}
private void send(String subjectKey, String template, Map<String, Object> attributes) throws EmailException {
send(subjectKey, Collections.emptyList(), template, attributes);
}
private void send(String subjectKey, List<Object> subjectAttributes, String template, Map<String, Object> attributes) throws EmailException {
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
@ -118,7 +155,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
attributes.put("locale", locale);
Properties rb = theme.getMessages(locale);
attributes.put("msg", new MessageFormatterMethod(locale, rb));
String subject = new MessageFormat(rb.getProperty(subjectKey,subjectKey),locale).format(new Object[0]);
String subject = new MessageFormat(rb.getProperty(subjectKey,subjectKey),locale).format(subjectAttributes.toArray());
String textTemplate = String.format("text/%s", template);
String textBody;
try {

View file

@ -5,6 +5,8 @@ package org.keycloak.login;
*/
public enum LoginFormsPages {
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL,
LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
}

View file

@ -23,6 +23,13 @@ import org.keycloak.provider.Provider;
*/
public interface LoginFormsProvider extends Provider {
String UPDATE_PROFILE_CONTEXT_ATTR = "updateProfileCtx";
String IDENTITY_PROVIDER_BROKER_CONTEXT = "identityProviderBrokerCtx";
String USERNAME_EDIT_DISABLED = "usernameEditDisabled";
/**
* Adds a script to the html header
*
@ -44,6 +51,12 @@ public interface LoginFormsProvider extends Provider {
public Response createInfoPage();
public Response createUpdateProfilePage();
public Response createIdpLinkConfirmLinkPage();
public Response createIdpLinkEmailPage();
public Response createErrorPage();
public Response createOAuthGrant(ClientSessionModel clientSessionModel);

View file

@ -19,6 +19,9 @@ package org.keycloak.login.freemarker;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
@ -48,6 +51,7 @@ import org.keycloak.login.freemarker.model.UrlBean;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
@ -129,6 +133,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
page = LoginFormsPages.LOGIN_CONFIG_TOTP;
break;
case UPDATE_PROFILE:
UpdateProfileContext userBasedContext = new UserUpdateProfileContext(realm, user);
this.attributes.put(UPDATE_PROFILE_CONTEXT_ATTR, userBasedContext);
actionMessage = Messages.UPDATE_PROFILE;
page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
break;
@ -140,7 +147,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
try {
UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
builder.queryParam(OAuth2Constants.CODE, accessCode);
builder.queryParam("key", clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
builder.queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY));
String link = builder.build(realm.getName()).toString();
long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
@ -222,6 +229,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
}
attributes.put("message", wholeMessage);
} else {
attributes.put("message", null);
}
attributes.put("messagesPerField", messagesPerField);
@ -237,7 +246,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (realm != null) {
attributes.put("realm", new RealmBean(realm));
attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
attributes.put("social", new IdentityProviderBean(realm, identityProviders, baseUri, uriInfo));
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
if (realm.isInternationalizationEnabled()) {
@ -268,7 +281,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
attributes.put("totp", new TotpBean(realm, user, baseUri));
break;
case LOGIN_UPDATE_PROFILE:
attributes.put("user", new ProfileBean(user, formData));
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
attributes.put("user", new ProfileBean(userCtx, formData));
break;
case LOGIN_IDP_LINK_CONFIRM:
case LOGIN_IDP_LINK_EMAIL:
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
String idpAlias = brokerContext.getIdpConfig().getAlias();
idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
attributes.put("brokerContext", brokerContext);
attributes.put("idpAlias", idpAlias);
break;
case REGISTER:
attributes.put("register", new RegisterBean(formData));
@ -371,7 +394,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (realm != null) {
attributes.put("realm", new RealmBean(realm));
attributes.put("social", new IdentityProviderBean(realm, baseUri, uriInfo));
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
attributes.put("social", new IdentityProviderBean(realm, identityProviders, baseUri, uriInfo));
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
@ -423,6 +450,32 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
return createResponse(LoginFormsPages.INFO);
}
@Override
public Response createUpdateProfilePage() {
// Don't display initial message if we already have some errors
if (messageType != MessageType.ERROR) {
setMessage(MessageType.WARNING, Messages.UPDATE_PROFILE);
}
return createResponse(LoginFormsPages.LOGIN_UPDATE_PROFILE);
}
@Override
public Response createIdpLinkConfirmLinkPage() {
return createResponse(LoginFormsPages.LOGIN_IDP_LINK_CONFIRM);
}
@Override
public Response createIdpLinkEmailPage() {
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
String idpAlias = brokerContext.getIdpConfig().getAlias();
idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);
}
@Override
public Response createErrorPage() {
if (status == null) {

View file

@ -0,0 +1,58 @@
package org.keycloak.login.freemarker;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
* Various util methods, so the logic is not hardcoded in freemarker beans
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LoginFormsUtil {
// Display just those identityProviders on login screen, which are already linked to "known" established user
public static List<IdentityProviderModel> filterIdentityProviders(List<IdentityProviderModel> providers, KeycloakSession session, RealmModel realm,
Map<String, Object> attributes, MultivaluedMap<String, String> formData) {
Boolean usernameEditDisabled = (Boolean) attributes.get(LoginFormsProvider.USERNAME_EDIT_DISABLED);
if (usernameEditDisabled != null && usernameEditDisabled) {
String username = formData.getFirst(UserModel.USERNAME);
if (username == null) {
throw new IllegalStateException("USERNAME_EDIT_DISABLED but username not known");
}
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null || !user.isEnabled()) {
throw new IllegalStateException("User " + username + " not found or disabled");
}
Set<FederatedIdentityModel> fedLinks = session.users().getFederatedIdentities(user, realm);
Set<String> federatedIdentities = new HashSet<>();
for (FederatedIdentityModel fedLink : fedLinks) {
federatedIdentities.add(fedLink.getIdentityProvider());
}
List<IdentityProviderModel> result = new LinkedList<>();
for (IdentityProviderModel idp : providers) {
if (federatedIdentities.contains(idp.getAlias())) {
result.add(idp);
}
}
return result;
} else {
return providers;
}
}
}

View file

@ -17,6 +17,10 @@ public class Templates {
return "login-config-totp.ftl";
case LOGIN_VERIFY_EMAIL:
return "login-verify-email.ftl";
case LOGIN_IDP_LINK_CONFIRM:
return "login-idp-link-confirm.ftl";
case LOGIN_IDP_LINK_EMAIL:
return "login-idp-link-email.ftl";
case OAUTH_GRANT:
return "login-oauth-grant.ftl";
case LOGIN_RESET_PASSWORD:

View file

@ -44,9 +44,8 @@ public class IdentityProviderBean {
private List<IdentityProvider> providers;
private RealmModel realm;
public IdentityProviderBean(RealmModel realm, URI baseURI, UriInfo uriInfo) {
public IdentityProviderBean(RealmModel realm, List<IdentityProviderModel> identityProviders, URI baseURI, UriInfo uriInfo) {
this.realm = realm;
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
if (!identityProviders.isEmpty()) {
Set<IdentityProvider> orderedSet = new TreeSet<>(IdentityProviderComparator.INSTANCE);
@ -57,7 +56,7 @@ public class IdentityProviderBean {
}
if (!orderedSet.isEmpty()) {
providers = new LinkedList<IdentityProvider>(orderedSet);
providers = new LinkedList<>(orderedSet);
displaySocial = true;
}
}

View file

@ -28,7 +28,7 @@ import java.util.Map;
import javax.ws.rs.core.MultivaluedMap;
import org.jboss.logging.Logger;
import org.keycloak.models.UserModel;
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -38,12 +38,12 @@ public class ProfileBean {
private static final Logger logger = Logger.getLogger(ProfileBean.class);
private UserModel user;
private UpdateProfileContext user;
private MultivaluedMap<String, String> formData;
private final Map<String, String> attributes = new HashMap<>();
public ProfileBean(UserModel user, MultivaluedMap<String, String> formData) {
public ProfileBean(UpdateProfileContext user, MultivaluedMap<String, String> formData) {
this.user = user;
this.formData = formData;
@ -70,6 +70,10 @@ public class ProfileBean {
}
public boolean isEditUsernameAllowed() {
return user.isEditUsernameAllowed();
}
public String getUsername() { return formData != null ? formData.getFirst("username") : user.getUsername(); }
public String getFirstName() {

View file

@ -90,6 +90,10 @@ public class UrlBean {
return Urls.loginActionEmailVerification(baseURI, realm).toString();
}
public String getFirstBrokerLoginUrl() {
return Urls.firstBrokerLoginProcessor(baseURI, realm).toString();
}
public String getOauthAction() {
if (this.actionuri != null) {
return this.actionuri.getPath();

View file

@ -12,7 +12,7 @@
<artifactId>keycloak-jetty-adapter-spi</artifactId>
<name>Keycloak Jetty Adapter SPI</name>
<properties>
<jetty9.version>8.1.16.v20140903</jetty9.version>
<jetty9.version>8.1.17.v20150415</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.spi.*
</keycloak.osgi.export>

View file

@ -3,10 +3,7 @@ package org.keycloak.adapters.springsecurity;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import java.io.FileNotFoundException;
@ -20,16 +17,17 @@ import java.io.IOException;
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
* @version $Revision: 1 $
*/
public class AdapterDeploymentContextBean implements ApplicationContextAware, InitializingBean {
public class AdapterDeploymentContextBean implements InitializingBean {
private static final String KEYCLOAK_CONFIG_FILE = "keycloak.json";
private static final String KEYCLOAK_CONFIG_WEB_RESOURCE = "WEB-INF/" + KEYCLOAK_CONFIG_FILE;
private static final String KEYCLOAK_CONFIG_CLASSPATH_RESOURCE = "classpath:" + KEYCLOAK_CONFIG_FILE;
private final Resource keycloakConfigFileResource;
private ApplicationContext applicationContext;
private AdapterDeploymentContext deploymentContext;
private KeycloakDeployment deployment;
public AdapterDeploymentContextBean(Resource keycloakConfigFileResource) {
this.keycloakConfigFileResource = keycloakConfigFileResource;
}
@Override
public void afterPropertiesSet() throws Exception {
this.deployment = loadKeycloakDeployment();
@ -38,17 +36,12 @@ public class AdapterDeploymentContextBean implements ApplicationContextAware, In
private KeycloakDeployment loadKeycloakDeployment() throws IOException {
Resource resource = applicationContext.getResource(KEYCLOAK_CONFIG_WEB_RESOURCE);
if (!resource.isReadable()) {
resource= applicationContext.getResource(KEYCLOAK_CONFIG_CLASSPATH_RESOURCE);
if (!keycloakConfigFileResource.isReadable()) {
throw new FileNotFoundException(String.format("Unable to locate Keycloak configuration file: %s",
keycloakConfigFileResource.getFilename()));
}
if (!resource.isReadable()) {
throw new FileNotFoundException(String.format("Unable to locate Keycloak from %s or %s", KEYCLOAK_CONFIG_WEB_RESOURCE, KEYCLOAK_CONFIG_CLASSPATH_RESOURCE));
}
return KeycloakDeploymentBuilder.build(resource.getInputStream());
return KeycloakDeploymentBuilder.build(keycloakConfigFileResource.getInputStream());
}
/**
@ -68,9 +61,4 @@ public class AdapterDeploymentContextBean implements ApplicationContextAware, In
public KeycloakDeployment getDeployment() {
return deployment;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View file

@ -8,7 +8,9 @@ import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcess
import org.keycloak.adapters.springsecurity.filter.KeycloakCsrfRequestMatcher;
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
@ -26,19 +28,20 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
*
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
* @version $Revision: 1 $
*
* @see EnableWebSecurity
* @see EnableWebMvcSecurity
*/
public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
@Value("${keycloak.configurationFile:WEB-INF/keycloak.json}")
private Resource keycloakConfigFileResource;
@Bean
protected AdapterDeploymentContextBean adapterDeploymentContextBean() {
return new AdapterDeploymentContextBean();
return new AdapterDeploymentContextBean(keycloakConfigFileResource);
}
protected AuthenticationEntryPoint authenticationEntryPoint()
{
protected AuthenticationEntryPoint authenticationEntryPoint() {
return new KeycloakAuthenticationEntryPoint();
}

View file

@ -0,0 +1,56 @@
package org.keycloak.adapters.springsecurity;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.FileNotFoundException;
import static org.junit.Assert.assertNotNull;
public class AdapterDeploymentContextBeanTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
private AdapterDeploymentContextBean adapterDeploymentContextBean;
@Test
public void should_create_deployment_and_deployment_context() throws Exception {
//given:
adapterDeploymentContextBean = new AdapterDeploymentContextBean(getCorrectResource());
//when:
adapterDeploymentContextBean.afterPropertiesSet();
//then
assertNotNull(adapterDeploymentContextBean.getDeployment());
assertNotNull(adapterDeploymentContextBean.getDeploymentContext());
}
private Resource getCorrectResource() {
return new ClassPathResource("keycloak.json");
}
@Test
public void should_throw_exception_when_configuration_file_was_not_found() throws Exception {
//given:
adapterDeploymentContextBean = new AdapterDeploymentContextBean(getEmptyResource());
//then:
expectedException.expect(FileNotFoundException.class);
expectedException.expectMessage("Unable to locate Keycloak configuration file: no-file.json");
//when:
adapterDeploymentContextBean.afterPropertiesSet();
}
private Resource getEmptyResource() {
return new ClassPathResource("no-file.json");
}
}

View file

@ -0,0 +1,10 @@
{
"realm": "spring-security",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh65Gqi3BSaVe12JHlqChWm8WscICrj46MVqmRoO9FCmqbxEpCQhE1RLjW+GDyc3YdXW3xqUQ3AZxDkTmN1h6BWkhdxPLzA4EnwgWmGurhyJlUF9Id2tKns0jbC+Z7kIb2LcOiKHKL7mRb3q7EtWubNnrvunv8fx+WeXGaQoGEVQIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "some-resource",
"credentials": {
"secret": "a9c3501e-20dd-4277-8a7b-351063848446"
}
}

View file

@ -23,5 +23,6 @@ public interface Constants {
// 30 days
int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
public static final String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
String KEY = "key";
}

View file

@ -46,14 +46,6 @@ public class IdentityProviderModel implements Serializable {
private boolean enabled;
/**
* For possible values see {@link IdentityProviderRepresentation#getUpdateProfileFirstLoginMode()}
* @see IdentityProviderRepresentation#UPFLM_ON
* @see IdentityProviderRepresentation#UPFLM_MISSING
* @see IdentityProviderRepresentation#UPFLM_OFF
*/
protected String updateProfileFirstLoginMode = IdentityProviderRepresentation.UPFLM_ON;
private boolean trustEmail;
private boolean storeToken;
@ -64,6 +56,8 @@ public class IdentityProviderModel implements Serializable {
*/
private boolean authenticateByDefault;
private String firstBrokerLoginFlowId;
/**
* <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
* in the map are understood by the identity provider implementation.</p>
@ -79,11 +73,11 @@ public class IdentityProviderModel implements Serializable {
this.alias = model.getAlias();
this.config = new HashMap<String, String>(model.getConfig());
this.enabled = model.isEnabled();
this.updateProfileFirstLoginMode = model.getUpdateProfileFirstLoginMode();
this.trustEmail = model.isTrustEmail();
this.storeToken = model.isStoreToken();
this.authenticateByDefault = model.isAuthenticateByDefault();
this.addReadTokenRoleOnCreate = model.addReadTokenRoleOnCreate;
this.firstBrokerLoginFlowId = model.getFirstBrokerLoginFlowId();
}
public String getInternalId() {
@ -118,20 +112,6 @@ public class IdentityProviderModel implements Serializable {
this.enabled = enabled;
}
/**
* @see IdentityProviderRepresentation#getUpdateProfileFirstLoginMode()
*/
public String getUpdateProfileFirstLoginMode() {
return updateProfileFirstLoginMode;
}
/**
* @see IdentityProviderRepresentation#setUpdateProfileFirstLoginMode(String)
*/
public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
}
public boolean isStoreToken() {
return this.storeToken;
}
@ -148,6 +128,14 @@ public class IdentityProviderModel implements Serializable {
this.authenticateByDefault = authenticateByDefault;
}
public String getFirstBrokerLoginFlowId() {
return firstBrokerLoginFlowId;
}
public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
}
public Map<String, String> getConfig() {
return this.config;
}

View file

@ -30,11 +30,11 @@ public class IdentityProviderEntity {
private String providerId;
private String name;
private boolean enabled;
private String updateProfileFirstLoginMode;
private boolean trustEmail;
private boolean storeToken;
protected boolean addReadTokenRoleOnCreate;
private boolean authenticateByDefault;
private String firstBrokerLoginFlowId;
private Map<String, String> config = new HashMap<String, String>();
@ -62,14 +62,6 @@ public class IdentityProviderEntity {
this.enabled = enabled;
}
public String getUpdateProfileFirstLoginMode() {
return updateProfileFirstLoginMode;
}
public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
}
public boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
@ -78,6 +70,14 @@ public class IdentityProviderEntity {
this.authenticateByDefault = authenticateByDefault;
}
public String getFirstBrokerLoginFlowId() {
return firstBrokerLoginFlowId;
}
public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
}
public boolean isStoreToken() {
return this.storeToken;
}

View file

@ -1,9 +1,14 @@
package org.keycloak.models.utils;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -19,6 +24,9 @@ public class DefaultAuthenticationFlows {
public static final String LOGIN_FORMS_FLOW = "forms";
public static final String CLIENT_AUTHENTICATION_FLOW = "clients";
public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login";
public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config";
public static void addFlows(RealmModel realm) {
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
@ -26,6 +34,7 @@ public class DefaultAuthenticationFlows {
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
}
public static void migrateFlows(RealmModel realm) {
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
@ -33,6 +42,7 @@ public class DefaultAuthenticationFlows {
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
}
public static void registrationFlow(RealmModel realm) {
@ -309,4 +319,115 @@ public class DefaultAuthenticationFlows {
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
public static void firstBrokerLoginFlow(RealmModel realm) {
AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW);
firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account");
firstBrokerLogin.setProviderId("basic-flow");
firstBrokerLogin.setTopLevel(true);
firstBrokerLogin.setBuiltIn(true);
firstBrokerLogin = realm.addAuthenticationFlow(firstBrokerLogin);
AuthenticatorConfigModel reviewProfileConfig = new AuthenticatorConfigModel();
reviewProfileConfig.setAlias(IDP_REVIEW_PROFILE_CONFIG_ALIAS);
Map<String, String> config = new HashMap<>();
config.put("update.profile.on.first.login", IdentityProviderRepresentation.UPFLM_MISSING);
reviewProfileConfig.setConfig(config);
reviewProfileConfig = realm.addAuthenticatorConfig(reviewProfileConfig);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(firstBrokerLogin.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("idp-review-profile");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
execution.setAuthenticatorConfig(reviewProfileConfig.getId());
realm.addAuthenticatorExecution(execution);
AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel();
createUserIfUniqueConfig.setAlias("create unique user config");
config = new HashMap<>();
config.put("require.password.update.after.registration", "false");
createUserIfUniqueConfig.setConfig(config);
createUserIfUniqueConfig = realm.addAuthenticatorConfig(createUserIfUniqueConfig);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(firstBrokerLogin.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("idp-create-user-if-unique");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
execution.setAuthenticatorConfig(createUserIfUniqueConfig.getId());
realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel();
linkExistingAccountFlow.setTopLevel(false);
linkExistingAccountFlow.setBuiltIn(true);
linkExistingAccountFlow.setAlias("Handle Existing Account");
linkExistingAccountFlow.setDescription("Handle what to do if there is existing account with same email/username like authenticated identity provider");
linkExistingAccountFlow.setProviderId("basic-flow");
linkExistingAccountFlow = realm.addAuthenticationFlow(linkExistingAccountFlow);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(firstBrokerLogin.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setFlowId(linkExistingAccountFlow.getId());
execution.setPriority(30);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(linkExistingAccountFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("idp-confirm-link");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(linkExistingAccountFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator("idp-email-verification");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel verifyByReauthenticationAccountFlow = new AuthenticationFlowModel();
verifyByReauthenticationAccountFlow.setTopLevel(false);
verifyByReauthenticationAccountFlow.setBuiltIn(true);
verifyByReauthenticationAccountFlow.setAlias("Verify Existing Account by Re-authentication");
verifyByReauthenticationAccountFlow.setDescription("Reauthentication of existing account");
verifyByReauthenticationAccountFlow.setProviderId("basic-flow");
verifyByReauthenticationAccountFlow = realm.addAuthenticationFlow(verifyByReauthenticationAccountFlow);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(linkExistingAccountFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setFlowId(verifyByReauthenticationAccountFlow.getId());
execution.setPriority(30);
execution.setAuthenticatorFlow(true);
realm.addAuthenticatorExecution(execution);
// password + otp
execution = new AuthenticationExecutionModel();
execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator("idp-username-password-form");
execution.setPriority(10);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
// TODO: read the requirement from browser authenticator
// if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
// execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
// }
execution.setAuthenticator("auth-otp-form");
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
}

View file

@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
@ -266,7 +267,6 @@ public final class KeycloakModelUtils {
}
/**
*
* @param roles
* @param targetRole
* @return true if targetRole is in roles (directly or indirectly via composite role)

View file

@ -257,7 +257,7 @@ public class ModelToRepresentation {
}
for (IdentityProviderModel provider : realm.getIdentityProviders()) {
rep.addIdentityProvider(toRepresentation(provider));
rep.addIdentityProvider(toRepresentation(realm, provider));
}
for (IdentityProviderMapperModel mapper : realm.getIdentityProviderMappers()) {
@ -436,7 +436,7 @@ public class ModelToRepresentation {
return rep;
}
public static IdentityProviderRepresentation toRepresentation(IdentityProviderModel identityProviderModel) {
public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation();
providerRep.setInternalId(identityProviderModel.getInternalId());
@ -444,12 +444,20 @@ public class ModelToRepresentation {
providerRep.setAlias(identityProviderModel.getAlias());
providerRep.setEnabled(identityProviderModel.isEnabled());
providerRep.setStoreToken(identityProviderModel.isStoreToken());
providerRep.setUpdateProfileFirstLoginMode(identityProviderModel.getUpdateProfileFirstLoginMode());
providerRep.setTrustEmail(identityProviderModel.isTrustEmail());
providerRep.setAuthenticateByDefault(identityProviderModel.isAuthenticateByDefault());
providerRep.setConfig(identityProviderModel.getConfig());
providerRep.setAddReadTokenRoleOnCreate(identityProviderModel.isAddReadTokenRoleOnCreate());
String firstBrokerLoginFlowId = identityProviderModel.getFirstBrokerLoginFlowId();
if (firstBrokerLoginFlowId != null) {
AuthenticationFlowModel flow = realm.getAuthenticationFlowById(firstBrokerLoginFlowId);
if (flow == null) {
throw new ModelException("Couldn't find authentication flow with id " + firstBrokerLoginFlowId);
}
providerRep.setFirstBrokerLoginFlowAlias(flow.getAlias());
}
return providerRep;
}

View file

@ -164,6 +164,16 @@ public class RepresentationToModel {
if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
importAuthenticationFlows(newRealm, rep);
if (rep.getRequiredActions() != null) {
for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
RequiredActionProviderModel model = toModel(action);
newRealm.addRequiredActionProvider(model);
}
} else {
DefaultRequiredActions.addActions(newRealm);
}
importIdentityProviders(rep, newRealm);
importIdentityProviderMappers(rep, newRealm);
@ -318,16 +328,6 @@ public class RepresentationToModel {
if(rep.getDefaultLocale() != null){
newRealm.setDefaultLocale(rep.getDefaultLocale());
}
importAuthenticationFlows(newRealm, rep);
if (rep.getRequiredActions() != null) {
for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
RequiredActionProviderModel model = toModel(action);
newRealm.addRequiredActionProvider(model);
}
} else {
DefaultRequiredActions.addActions(newRealm);
}
}
public static void importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) {
@ -1062,7 +1062,7 @@ public class RepresentationToModel {
private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) {
if (rep.getIdentityProviders() != null) {
for (IdentityProviderRepresentation representation : rep.getIdentityProviders()) {
newRealm.addIdentityProvider(toModel(representation));
newRealm.addIdentityProvider(toModel(newRealm, representation));
}
}
}
@ -1073,20 +1073,30 @@ public class RepresentationToModel {
}
}
}
public static IdentityProviderModel toModel(IdentityProviderRepresentation representation) {
public static IdentityProviderModel toModel(RealmModel realm, IdentityProviderRepresentation representation) {
IdentityProviderModel identityProviderModel = new IdentityProviderModel();
identityProviderModel.setInternalId(representation.getInternalId());
identityProviderModel.setAlias(representation.getAlias());
identityProviderModel.setProviderId(representation.getProviderId());
identityProviderModel.setEnabled(representation.isEnabled());
identityProviderModel.setUpdateProfileFirstLoginMode(representation.getUpdateProfileFirstLoginMode());
identityProviderModel.setTrustEmail(representation.isTrustEmail());
identityProviderModel.setAuthenticateByDefault(representation.isAuthenticateByDefault());
identityProviderModel.setStoreToken(representation.isStoreToken());
identityProviderModel.setAddReadTokenRoleOnCreate(representation.isAddReadTokenRoleOnCreate());
identityProviderModel.setConfig(representation.getConfig());
String flowAlias = representation.getFirstBrokerLoginFlowAlias();
if (flowAlias == null) {
flowAlias = DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW;
}
AuthenticationFlowModel flowModel = realm.getFlowByAlias(flowAlias);
if (flowModel == null) {
throw new ModelException("No available authentication flow with alias: " + flowAlias);
}
identityProviderModel.setFirstBrokerLoginFlowId(flowModel.getId());
return identityProviderModel;
}

View file

@ -1218,9 +1218,9 @@ public class RealmAdapter implements RealmModel {
identityProviderModel.setInternalId(entity.getInternalId());
identityProviderModel.setConfig(entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode());
identityProviderModel.setTrustEmail(entity.isTrustEmail());
identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
identityProviderModel.setStoreToken(entity.isStoreToken());
identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
@ -1251,9 +1251,9 @@ public class RealmAdapter implements RealmModel {
entity.setEnabled(identityProvider.isEnabled());
entity.setStoreToken(identityProvider.isStoreToken());
entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
entity.setTrustEmail(identityProvider.isTrustEmail());
entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
entity.setConfig(identityProvider.getConfig());
realm.addIdentityProvider(entity);
@ -1278,9 +1278,9 @@ public class RealmAdapter implements RealmModel {
if (entity.getInternalId().equals(identityProvider.getInternalId())) {
entity.setAlias(identityProvider.getAlias());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
entity.setTrustEmail(identityProvider.isTrustEmail());
entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
entity.setStoreToken(identityProvider.isStoreToken());
entity.setConfig(identityProvider.getConfig());

View file

@ -11,6 +11,7 @@ import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.util.Map;
@ -41,9 +42,6 @@ public class IdentityProviderEntity {
@Column(name="ENABLED")
private boolean enabled;
@Column(name = "UPDATE_PROFILE_FIRST_LGN_MD")
private String updateProfileFirstLoginMode;
@Column(name = "TRUST_EMAIL")
private boolean trustEmail;
@ -56,6 +54,9 @@ public class IdentityProviderEntity {
@Column(name="AUTHENTICATE_BY_DEFAULT")
private boolean authenticateByDefault;
@Column(name="FIRST_BROKER_LOGIN_FLOW_ID")
private String firstBrokerLoginFlowId;
@ElementCollection
@MapKeyColumn(name="NAME")
@Column(name="VALUE", columnDefinition = "TEXT")
@ -102,14 +103,6 @@ public class IdentityProviderEntity {
this.enabled = enabled;
}
public String getUpdateProfileFirstLoginMode() {
return updateProfileFirstLoginMode;
}
public void setUpdateProfileFirstLoginMode(String updateProfileFirstLoginMode) {
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
}
public boolean isStoreToken() {
return this.storeToken;
}
@ -126,6 +119,14 @@ public class IdentityProviderEntity {
this.authenticateByDefault = authenticateByDefault;
}
public String getFirstBrokerLoginFlowId() {
return firstBrokerLoginFlowId;
}
public void setFirstBrokerLoginFlowId(String firstBrokerLoginFlowId) {
this.firstBrokerLoginFlowId = firstBrokerLoginFlowId;
}
public Map<String, String> getConfig() {
return this.config;
}

View file

@ -120,7 +120,7 @@ public class RealmEntity {
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") })
@JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name = "USERFEDERATIONPROVIDERS_ID") })
List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")

View file

@ -30,7 +30,7 @@ import java.util.Collection;
public class RoleEntity {
@Id
@Column(name="id", length = 36)
@Column(name="ID", length = 36)
private String id;
@Column(name = "NAME")

View file

@ -77,7 +77,7 @@ public class UserEntity {
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
@Column(name="federation_link")
@Column(name="FEDERATION_LINK")
protected String federationLink;
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")

View file

@ -35,7 +35,7 @@ public class UserFederationProviderEntity {
@ElementCollection
@MapKeyColumn(name="name")
@Column(name="value")
@Column(name="VALUE")
@CollectionTable(name="USER_FEDERATION_CONFIG", joinColumns={ @JoinColumn(name="USER_FEDERATION_PROVIDER_ID") })
private Map<String, String> config;

View file

@ -898,9 +898,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
identityProviderModel.setInternalId(entity.getInternalId());
identityProviderModel.setConfig(entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLoginMode(entity.getUpdateProfileFirstLoginMode());
identityProviderModel.setTrustEmail(entity.isTrustEmail());
identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
identityProviderModel.setStoreToken(entity.isStoreToken());
identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
@ -929,11 +929,11 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setAlias(identityProvider.getAlias());
entity.setProviderId(identityProvider.getProviderId());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
entity.setTrustEmail(identityProvider.isTrustEmail());
entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
entity.setStoreToken(identityProvider.isStoreToken());
entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
entity.setConfig(identityProvider.getConfig());
realm.getIdentityProviders().add(entity);
@ -957,9 +957,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
if (entity.getInternalId().equals(identityProvider.getInternalId())) {
entity.setAlias(identityProvider.getAlias());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLoginMode(identityProvider.getUpdateProfileFirstLoginMode());
entity.setTrustEmail(identityProvider.isTrustEmail());
entity.setAuthenticateByDefault(identityProvider.isAuthenticateByDefault());
entity.setFirstBrokerLoginFlowId(identityProvider.getFirstBrokerLoginFlowId());
entity.setAddReadTokenRoleOnCreate(identityProvider.isAddReadTokenRoleOnCreate());
entity.setStoreToken(identityProvider.isStoreToken());
entity.setConfig(identityProvider.getConfig());

View file

@ -32,11 +32,6 @@
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>

View file

@ -76,6 +76,11 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
* @throws {@link IllegalArgumentException} when the configStream is null
*/
public Object parse(InputStream configStream) throws ParsingException {
XMLEventReader xmlEventReader = createEventReader(configStream);
return parse(xmlEventReader);
}
public XMLEventReader createEventReader(InputStream configStream) throws ParsingException {
if (configStream == null)
throw logger.nullArgumentError("InputStream");
@ -105,7 +110,7 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
throw logger.parserException(e);
}
return parse(xmlEventReader);
return xmlEventReader;
}
private ClassLoader getTCCL() {

View file

@ -1,85 +0,0 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.saml.common.util;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
/**
* Utility dealing with Password Based Encryption (Code is ripped off of the PBEUtils class in JBossSecurity/PicketBox)
*
* @author Scott.Stark@jboss.org
* @author Anil.Saldhana@redhat.com
* @since May 25, 2010
*/
public class PBEUtils {
public static byte[] encode(byte[] secret, String cipherAlgorithm, SecretKey cipherKey, PBEParameterSpec cipherSpec)
throws Exception {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, cipherKey, cipherSpec);
byte[] encoding = cipher.doFinal(secret);
return encoding;
}
public static String encode64(byte[] secret, String cipherAlgorithm, SecretKey cipherKey, PBEParameterSpec cipherSpec)
throws Exception {
byte[] encoding = encode(secret, cipherAlgorithm, cipherKey, cipherSpec);
String b64 = Base64.encodeBytes(encoding);
return b64;
}
public static byte[] decode(byte[] secret, String cipherAlgorithm, SecretKey cipherKey, PBEParameterSpec cipherSpec)
throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherSpec);
byte[] decode = cipher.doFinal(secret);
return decode;
}
public static String decode64(String secret, String cipherAlgorithm, SecretKey cipherKey, PBEParameterSpec cipherSpec)
throws GeneralSecurityException, UnsupportedEncodingException {
byte[] encoding = Base64.decode(secret);
byte[] decode = decode(encoding, cipherAlgorithm, cipherKey, cipherSpec);
return new String(decode, "UTF-8");
}
public static void main(String[] args) throws Exception {
if (args.length != 3) {
System.err.println("Encrypt a password" + "Usage: PBEUtils salt count domain-password password"
+ " salt : the Salt " + " count : the IterationCount "
+ " password : the plaintext password that should be encrypted");
throw new RuntimeException(" ERROR: please see format above");
}
byte[] salt = args[0].substring(0, 8).getBytes();
int count = Integer.parseInt(args[1]);
char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
byte[] passwordToEncode = args[2].getBytes("UTF-8");
PBEParameterSpec cipherSpec = new PBEParameterSpec(salt, count);
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEwithMD5andDES");
SecretKey cipherKey = factory.generateSecret(keySpec);
String encodedPassword = encode64(passwordToEncode, "PBEwithMD5andDES", cipherKey, cipherSpec);
System.err.println("Encoded password: MASK-" + encodedPassword);
}
}

View file

@ -180,36 +180,6 @@ public class StringUtil {
return map;
}
/**
* Given a masked password {@link String}, decode it
*
* @param maskedString a password string that is masked
* @param salt Salt
* @param iterationCount Iteration Count
*
* @return Decoded String
*
* @throws Exception
*/
public static String decode(String maskedString, String salt, int iterationCount) throws Exception {
String pbeAlgo = PicketLinkCommonConstants.PBE_ALGORITHM;
if (maskedString.startsWith(PicketLinkCommonConstants.PASS_MASK_PREFIX)) {
// Create the PBE secret key
SecretKeyFactory factory = SecretKeyFactory.getInstance(pbeAlgo);
char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(), iterationCount);
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKey cipherKey = factory.generateSecret(keySpec);
maskedString = maskedString.substring(PicketLinkCommonConstants.PASS_MASK_PREFIX.length());
String decodedValue = PBEUtils.decode64(maskedString, pbeAlgo, cipherKey, cipherSpec);
maskedString = decodedValue;
}
return maskedString;
}
public static String[] split(String toSplit, String delimiter) {
if (delimiter.length() != 1) {
throw new IllegalArgumentException("Delimiter can only be one character in length");

View file

@ -136,7 +136,7 @@ public class SAMLAssertionWriter extends BaseWriter {
if (statements != null) {
for (StatementAbstractType statement : statements) {
if (statement instanceof AuthnStatementType) {
write((AuthnStatementType) statement);
write((AuthnStatementType) statement, false);
} else if (statement instanceof AttributeStatementType) {
write((AttributeStatementType) statement);
} else
@ -188,8 +188,12 @@ public class SAMLAssertionWriter extends BaseWriter {
*
* @throws ProcessingException
*/
public void write(AuthnStatementType authnStatement) throws ProcessingException {
public void write(AuthnStatementType authnStatement, boolean includeNamespace) throws ProcessingException {
StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.AUTHN_STATEMENT.get(), ASSERTION_NSURI.get());
if (includeNamespace) {
StaxUtil.writeNameSpace(writer, ASSERTION_PREFIX, ASSERTION_NSURI.get());
StaxUtil.writeDefaultNameSpace(writer, ASSERTION_NSURI.get());
}
XMLGregorianCalendar authnInstant = authnStatement.getAuthnInstant();
if (authnInstant != null) {

View file

@ -22,5 +22,9 @@ public enum AuthenticationFlowError {
CLIENT_NOT_FOUND,
CLIENT_DISABLED,
CLIENT_CREDENTIALS_SETUP_REQUIRED,
INVALID_CLIENT_CREDENTIALS
INVALID_CLIENT_CREDENTIALS,
IDENTITY_PROVIDER_NOT_FOUND,
IDENTITY_PROVIDER_DISABLED,
IDENTITY_PROVIDER_ERROR
}

View file

@ -0,0 +1,118 @@
package org.keycloak.authentication.authenticators.broker;
import javax.ws.rs.core.Response;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationFlowException;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.events.Errors;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractIdpAuthenticator implements Authenticator {
// The clientSession note encapsulating all the BrokeredIdentityContext info. When this note is in clientSession, we know that firstBrokerLogin flow is in progress
public static final String BROKERED_CONTEXT_NOTE = "BROKERED_CONTEXT";
// The clientSession note with all the info about existing user
public static final String EXISTING_USER_INFO = "EXISTING_USER_INFO";
// The clientSession note flag to indicate that email provided by identityProvider was changed on updateProfile page
public static final String UPDATE_PROFILE_EMAIL_CHANGED = "UPDATE_PROFILE_EMAIL_CHANGED";
// The clientSession note flag to indicate if re-authentication after first broker login happened in different browser window. This can happen for example during email verification
public static final String IS_DIFFERENT_BROWSER = "IS_DIFFERENT_BROWSER";
// The clientSession note flag to indicate that updateProfile page will be always displayed even if "updateProfileOnFirstLogin" is off
public static final String ENFORCE_UPDATE_PROFILE = "ENFORCE_UPDATE_PROFILE";
// clientSession.note flag specifies if we imported new user to keycloak (true) or we just linked to an existing keycloak user (false)
public static final String BROKER_REGISTERED_NEW_USER = "BROKER_REGISTERED_NEW_USER";
@Override
public void authenticate(AuthenticationFlowContext context) {
ClientSessionModel clientSession = context.getClientSession();
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
if (serializedCtx == null) {
throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
}
BrokeredIdentityContext brokerContext = serializedCtx.deserialize(context.getSession(), clientSession);
if (!brokerContext.getIdpConfig().isEnabled()) {
sendFailureChallenge(context, Errors.IDENTITY_PROVIDER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
}
authenticateImpl(context, serializedCtx, brokerContext);
}
@Override
public void action(AuthenticationFlowContext context) {
ClientSessionModel clientSession = context.getClientSession();
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession);
if (serializedCtx == null) {
throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
}
BrokeredIdentityContext brokerContext = serializedCtx.deserialize(context.getSession(), clientSession);
if (!brokerContext.getIdpConfig().isEnabled()) {
sendFailureChallenge(context, Errors.IDENTITY_PROVIDER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
}
actionImpl(context, serializedCtx, brokerContext);
}
protected abstract void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext);
protected abstract void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext);
protected void sendFailureChallenge(AuthenticationFlowContext context, String eventError, String errorMessage, AuthenticationFlowError flowError) {
context.getEvent().user(context.getUser())
.error(eventError);
Response challengeResponse = context.form()
.setError(errorMessage)
.createErrorPage();
context.failureChallenge(flowError, challengeResponse);
}
@Override
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
}
@Override
public void close() {
}
public static UserModel getExistingUser(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
String existingUserId = clientSession.getNote(EXISTING_USER_INFO);
if (existingUserId == null) {
throw new AuthenticationFlowException("Unexpected state. There is no existing duplicated user identified in ClientSession",
AuthenticationFlowError.INTERNAL_ERROR);
}
ExistingUserInfo duplication = ExistingUserInfo.deserialize(existingUserId);
UserModel existingUser = session.users().getUserById(duplication.getExistingUserId(), realm);
if (existingUser == null) {
throw new AuthenticationFlowException("User with ID '" + existingUserId + "' not found.", AuthenticationFlowError.INVALID_USER);
}
if (!existingUser.isEnabled()) {
throw new AuthenticationFlowException("User with ID '" + existingUserId + "', username '" + existingUser.getUsername() + "' disabled.", AuthenticationFlowError.USER_DISABLED);
}
return existingUser;
}
}

View file

@ -0,0 +1,73 @@
package org.keycloak.authentication.authenticators.broker;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationFlowException;
import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpConfirmLinkAuthenticator extends AbstractIdpAuthenticator {
protected static Logger logger = Logger.getLogger(IdpConfirmLinkAuthenticator.class);
@Override
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
ClientSessionModel clientSession = context.getClientSession();
String existingUserInfo = clientSession.getNote(EXISTING_USER_INFO);
if (existingUserInfo == null) {
logger.warnf("No duplication detected.");
context.attempted();
return;
}
ExistingUserInfo duplicationInfo = ExistingUserInfo.deserialize(existingUserInfo);
Response challenge = context.form()
.setStatus(Response.Status.OK)
.setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
.setError(Messages.FEDERATED_IDENTITY_CONFIRM_LINK_MESSAGE, duplicationInfo.getDuplicateAttributeName(), duplicationInfo.getDuplicateAttributeValue())
.createIdpLinkConfirmLinkPage();
context.challenge(challenge);
}
@Override
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String action = formData.getFirst("submitAction");
if (action != null && action.equals("updateProfile")) {
context.getClientSession().setNote(ENFORCE_UPDATE_PROFILE, "true");
context.getClientSession().removeNote(EXISTING_USER_INFO);
context.resetFlow();
} else if (action != null && action.equals("linkAccount")) {
context.success();
} else {
throw new AuthenticationFlowException("Unknown action: " + action,
AuthenticationFlowError.INTERNAL_ERROR);
}
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return false;
}
}

View file

@ -0,0 +1,86 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpConfirmLinkAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "idp-confirm-link";
static IdpConfirmLinkAuthenticator SINGLETON = new IdpConfirmLinkAuthenticator();
@Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getReferenceCategory() {
return "confirmLink";
}
@Override
public boolean isConfigurable() {
return false;
}
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public String getDisplayType() {
return "Confirm link existing account";
}
@Override
public String getHelpText() {
return "Show the form where user confirms if he wants to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public boolean isUserSetupAllowed() {
return false;
}
}

View file

@ -0,0 +1,113 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.messages.Messages;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator {
protected static Logger logger = Logger.getLogger(IdpCreateUserIfUniqueAuthenticator.class);
@Override
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
}
@Override
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
if (context.getClientSession().getNote(EXISTING_USER_INFO) != null) {
context.attempted();
return;
}
ExistingUserInfo duplication = checkExistingUser(context, serializedCtx, brokerContext);
if (duplication == null) {
logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .",
brokerContext.getModelUsername(), brokerContext.getIdpConfig().getAlias());
UserModel federatedUser = session.users().addUser(realm, brokerContext.getModelUsername());
federatedUser.setEnabled(true);
federatedUser.setEmail(brokerContext.getEmail());
federatedUser.setFirstName(brokerContext.getFirstName());
federatedUser.setLastName(brokerContext.getLastName());
for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet()) {
federatedUser.setAttribute(attr.getKey(), attr.getValue());
}
AuthenticatorConfigModel config = context.getAuthenticatorConfig();
if (config != null && Boolean.parseBoolean(config.getConfig().get(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION))) {
logger.debugf("User '%s' required to update password", federatedUser.getUsername());
federatedUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}
// TODO: Event
context.setUser(federatedUser);
context.getClientSession().setNote(BROKER_REGISTERED_NEW_USER, "true");
context.success();
} else {
logger.debugf("Duplication detected. There is already existing user with %s '%s' .",
duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue());
// Set duplicated user, so next authenticators can deal with it
context.getClientSession().setNote(EXISTING_USER_INFO, duplication.serialize());
Response challengeResponse = context.form()
.setError(Messages.FEDERATED_IDENTITY_EXISTS, duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
.createErrorPage();
context.challenge(challengeResponse);
}
}
// Could be overriden to detect duplication based on other criterias (firstName, lastName, ...)
protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
if (brokerContext.getEmail() != null) {
UserModel existingUser = context.getSession().users().getUserByEmail(brokerContext.getEmail(), context.getRealm());
if (existingUser != null) {
return new ExistingUserInfo(existingUser.getId(), UserModel.EMAIL, existingUser.getEmail());
}
}
UserModel existingUser = context.getSession().users().getUserByUsername(brokerContext.getModelUsername(), context.getRealm());
if (existingUser != null) {
return new ExistingUserInfo(existingUser.getId(), UserModel.USERNAME, existingUser.getUsername());
}
return null;
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
}

View file

@ -0,0 +1,101 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.ArrayList;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpCreateUserIfUniqueAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "idp-create-user-if-unique";
static IdpCreateUserIfUniqueAuthenticator SINGLETON = new IdpCreateUserIfUniqueAuthenticator();
public static final String REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION = "require.password.update.after.registration";
@Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getReferenceCategory() {
return "createUserIfUnique";
}
@Override
public boolean isConfigurable() {
return true;
}
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public String getDisplayType() {
return "Create User If Unique";
}
@Override
public String getHelpText() {
return "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user";
}
@Override
public boolean isUserSetupAllowed() {
return false;
}
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION);
property.setLabel("Require Password Update After Registration");
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
property.setHelpText("If this option is true and new user is successfully imported from Identity Provider to Keycloak (there is no duplicated email or username detected in Keycloak DB), then this user is required to update his password");
configProperties.add(property);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}

View file

@ -0,0 +1,135 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.requiredactions.VerifyEmail;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator {
protected static Logger logger = Logger.getLogger(IdpEmailVerificationAuthenticator.class);
@Override
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
ClientSessionModel clientSession = context.getClientSession();
if (realm.getSmtpConfig().size() == 0) {
logger.warnf("Smtp is not configured for the realm. Ignoring email verification authenticator");
context.attempted();
return;
}
// Create action cookie to detect if email verification happened in same browser
LoginActionsService.createActionCookie(context.getRealm(), context.getUriInfo(), context.getConnection(), context.getClientSession().getId());
VerifyEmail.setupKey(clientSession);
UserModel existingUser = getExistingUser(session, realm, clientSession);
String link = UriBuilder.fromUri(context.getActionUrl())
.queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY))
.build().toString();
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
try {
context.getSession().getProvider(EmailProvider.class)
.setRealm(realm)
.setUser(existingUser)
.setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
.sendConfirmIdentityBrokerLink(link, expiration);
// event.clone().event(EventType.SEND_RESET_PASSWORD)
// .user(user)
// .detail(Details.USERNAME, username)
// .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
} catch (EmailException e) {
// event.clone().event(EventType.SEND_RESET_PASSWORD)
// .detail(Details.USERNAME, username)
// .user(user)
// .error(Errors.EMAIL_SEND_FAILED);
logger.error("Failed to send email to confirm identity broker linking", e);
Response challenge = context.form()
.setError(Messages.EMAIL_SENT_ERROR)
.createErrorPage();
context.failure(AuthenticationFlowError.INTERNAL_ERROR, challenge);
return;
}
Response challenge = context.form()
.setStatus(Response.Status.OK)
.setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
.createIdpLinkEmailPage();
context.forceChallenge(challenge);
}
@Override
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
String key = queryParams.getFirst(Constants.KEY);
ClientSessionModel clientSession = context.getClientSession();
RealmModel realm = context.getRealm();
KeycloakSession session = context.getSession();
if (key != null) {
String keyFromSession = clientSession.getNote(Constants.VERIFY_EMAIL_KEY);
clientSession.removeNote(Constants.VERIFY_EMAIL_KEY);
if (key.equals(keyFromSession)) {
UserModel existingUser = getExistingUser(session, realm, clientSession);
logger.debugf("User '%s' confirmed that wants to link with identity provider '%s' . Identity provider username is '%s' ", existingUser.getUsername(),
brokerContext.getIdpConfig().getAlias(), brokerContext.getUsername());
String actionCookieValue = LoginActionsService.getActionCookie(session.getContext().getRequestHeaders(), realm, session.getContext().getUri(), context.getConnection());
if (actionCookieValue == null || !actionCookieValue.equals(clientSession.getId())) {
clientSession.setNote(IS_DIFFERENT_BROWSER, "true");
}
context.setUser(existingUser);
context.success();
} else {
logger.error("Key parameter don't match with the expected value from client session");
Response challengeResponse = context.form()
.setError(Messages.INVALID_ACCESS_CODE)
.createErrorPage();
context.failureChallenge(AuthenticationFlowError.IDENTITY_PROVIDER_ERROR, challengeResponse);
}
} else {
Response challengeResponse = context.form()
.setError(Messages.MISSING_PARAMETER, Constants.KEY)
.createErrorPage();
context.failureChallenge(AuthenticationFlowError.IDENTITY_PROVIDER_ERROR, challengeResponse);
}
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return false;
}
}

View file

@ -0,0 +1,85 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpEmailVerificationAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "idp-email-verification";
static IdpEmailVerificationAuthenticator SINGLETON = new IdpEmailVerificationAuthenticator();
@Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getReferenceCategory() {
return "emailVerification";
}
@Override
public boolean isConfigurable() {
return false;
}
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public String getDisplayType() {
return "Verify existing account by Email";
}
@Override
public String getHelpText() {
return "Email verification of existing Keycloak user, that wants to link his user account with identity provider";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
@Override
public boolean isUserSetupAllowed() {
return false;
}
}

View file

@ -0,0 +1,126 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.List;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.resources.AttributeFormDataProcessor;
import org.keycloak.services.validation.Validation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator {
protected static Logger logger = Logger.getLogger(IdpReviewProfileAuthenticator.class);
@Override
public boolean requiresUser() {
return false;
}
@Override
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
IdentityProviderModel idpConfig = brokerContext.getIdpConfig();
if (requiresUpdateProfilePage(context, userCtx, brokerContext)) {
logger.debugf("Identity provider '%s' requires update profile action for broker user '%s'.", idpConfig.getAlias(), userCtx.getUsername());
// No formData for first render. The profile is rendered from userCtx
Response challengeResponse = context.form()
.setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx)
.setFormData(null)
.createUpdateProfilePage();
context.challenge(challengeResponse);
} else {
// Not required to update profile. Marked success
context.success();
}
}
protected boolean requiresUpdateProfilePage(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
String enforceUpdateProfile = context.getClientSession().getNote(ENFORCE_UPDATE_PROFILE);
if (Boolean.parseBoolean(enforceUpdateProfile)) {
return true;
}
String updateProfileFirstLogin;
AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
if (authenticatorConfig == null || !authenticatorConfig.getConfig().containsKey(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN)) {
updateProfileFirstLogin = IdentityProviderRepresentation.UPFLM_MISSING;
} else {
updateProfileFirstLogin = authenticatorConfig.getConfig().get(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN);
}
RealmModel realm = context.getRealm();
return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin)
|| (IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin) && !Validation.validateUserMandatoryFields(realm, userCtx));
}
@Override
protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) {
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_PROFILE);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
RealmModel realm = context.getRealm();
List<FormMessage> errors = Validation.validateUpdateProfileForm(true, formData);
if (errors != null && !errors.isEmpty()) {
Response challenge = context.form()
.setErrors(errors)
.setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx)
.setFormData(formData)
.createUpdateProfilePage();
context.challenge(challenge);
return;
}
userCtx.setUsername(formData.getFirst(UserModel.USERNAME));
userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME));
userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME));
String email = formData.getFirst(UserModel.EMAIL);
if (!ObjectUtil.isEqualOrBothNull(email, userCtx.getEmail())) {
if (logger.isTraceEnabled()) {
logger.tracef("Email updated on updateProfile page to '%s' ", email);
}
userCtx.setEmail(email);
context.getClientSession().setNote(UPDATE_PROFILE_EMAIL_CHANGED, "true");
}
AttributeFormDataProcessor.process(formData, realm, userCtx);
userCtx.saveToClientSession(context.getClientSession());
logger.debugf("Profile updated successfully after first authentication with identity provider '%s' for broker user '%s'.", brokerContext.getIdpConfig().getAlias(), userCtx.getUsername());
event.detail(Details.UPDATED_EMAIL, email);
context.success();
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
}

View file

@ -0,0 +1,107 @@
package org.keycloak.authentication.authenticators.broker;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpReviewProfileAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "idp-review-profile";
static IdpReviewProfileAuthenticator SINGLETON = new IdpReviewProfileAuthenticator();
public static final String UPDATE_PROFILE_ON_FIRST_LOGIN = "update.profile.on.first.login";
@Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getReferenceCategory() {
return "reviewProfile";
}
@Override
public boolean isConfigurable() {
return true;
}
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public String getDisplayType() {
return "Review Profile";
}
@Override
public String getHelpText() {
return "User reviews and updates profile data retrieved from Identity Provider in the displayed form";
}
@Override
public boolean isUserSetupAllowed() {
return false;
}
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(UPDATE_PROFILE_ON_FIRST_LOGIN);
property.setLabel("{{:: 'update-profile-on-first-login' | translate}}");
property.setType(ProviderConfigProperty.LIST_TYPE);
List<String> updateProfileValues = Arrays.asList(IdentityProviderRepresentation.UPFLM_ON, IdentityProviderRepresentation.UPFLM_MISSING, IdentityProviderRepresentation.UPFLM_OFF);
property.setDefaultValue(updateProfileValues);
property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that"
+ " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed."
+ " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one");
configProperties.add(property);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}

View file

@ -0,0 +1,55 @@
package org.keycloak.authentication.authenticators.broker;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationFlowException;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
/**
* Same like classic username+password form, but username is "known" and user can't change it
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpUsernamePasswordForm extends UsernamePasswordForm {
@Override
protected Response challenge(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
return setupForm(context, formData, existingUser)
.setStatus(Response.Status.OK)
.createLogin();
}
@Override
protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getClientSession());
context.setUser(existingUser);
// Restore formData for the case of error
setupForm(context, formData, existingUser);
return validatePassword(context, formData);
}
protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData, UserModel existingUser) {
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(context.getClientSession());
if (serializedCtx == null) {
throw new AuthenticationFlowException("Not found serialized context in clientSession", AuthenticationFlowError.IDENTITY_PROVIDER_ERROR);
}
formData.add(AuthenticationManager.FORM_USERNAME, existingUser.getUsername());
return context.form()
.setFormData(formData)
.setAttribute(LoginFormsProvider.USERNAME_EDIT_DISABLED, true)
.setSuccess(Messages.FEDERATED_IDENTITY_CONFIRM_REAUTHENTICATE_MESSAGE, existingUser.getUsername(), serializedCtx.getIdentityProviderId());
}
}

View file

@ -0,0 +1,35 @@
package org.keycloak.authentication.authenticators.broker;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.models.KeycloakSession;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class IdpUsernamePasswordFormFactory extends UsernamePasswordFormFactory {
public static final String PROVIDER_ID = "idp-username-password-form";
public static final UsernamePasswordForm IDP_SINGLETON = new IdpUsernamePasswordForm();
@Override
public Authenticator create(KeycloakSession session) {
return IDP_SINGLETON;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return "Validates a password from login form. Username is already known from identity provider authentication";
}
@Override
public String getDisplayType() {
return "Username Password Form for identity provider reauthentication";
}
}

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