Merge remote-tracking branch 'keycloak/master' into french-i18n

This commit is contained in:
gautric 2015-10-13 10:02:04 +02:00
commit fe9b5a6588
33 changed files with 120 additions and 622 deletions

View file

@ -141,22 +141,6 @@
<artifactId>mongo-java-driver</artifactId>
</dependency>
<!-- export/import -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-zip</artifactId>
</dependency>
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="de.idyl.winzipaes">
<resources>
<artifact name="${de.idyl:winzipaes}"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="org.bouncycastle"/>
</dependencies>
</module>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-export-import-zip">
<resources>
<artifact name="${org.keycloak:keycloak-export-import-zip}"/>
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-invalidation-cache-model"/>
<module name="org.keycloak.keycloak-export-import-api"/>
<module name="javax.ws.rs.api"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.codehaus.jackson.jackson-xc"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="de.idyl.winzipaes"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -21,7 +21,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -31,7 +31,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -278,15 +278,6 @@
<maven-resource group="org.mongodb" artifact="mongo-java-driver"/>
</module-def>
<!-- export/import -->
<module-def name="org.keycloak.keycloak-export-import-zip">
<maven-resource group="org.keycloak" artifact="keycloak-export-import-zip"/>
</module-def>
<module-def name="de.idyl.winzipaes">
<maven-resource group="de.idyl" artifact="winzipaes"/>
</module-def>
<module-def name="org.liquibase">
<maven-resource group="org.liquibase" artifact="liquibase-core"/>
</module-def>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="de.idyl.winzipaes">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -21,7 +21,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-export-import-zip">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-invalidation-cache-model"/>
<module name="org.keycloak.keycloak-export-import-api"/>
<module name="javax.ws.rs.api"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.codehaus.jackson.jackson-xc"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="org.jboss.logging"/>
<module name="org.bouncycastle" />
<module name="de.idyl.winzipaes"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -31,7 +31,6 @@
<module name="org.keycloak.keycloak-export-import-api" services="import"/>
<module name="org.keycloak.keycloak-export-import-dir" services="import"/>
<module name="org.keycloak.keycloak-export-import-single-file" services="import"/>
<module name="org.keycloak.keycloak-export-import-zip" services="import"/>
<module name="org.keycloak.keycloak-forms-common-freemarker" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
<module name="org.keycloak.keycloak-invalidation-cache-infinispan" services="import"/>

View file

@ -14,7 +14,6 @@
<outputDirectory>modules/system/layers/base</outputDirectory>
<includes>
<include>com/google/zxing/**</include>
<include>de/idyl/winzipaes/**</include>
<include>org/freemarker/**</include>
<include>org/keycloak/**</include>
<include>org/liquibase/**</include>

View file

@ -8,12 +8,11 @@
<para>
You can export/import your database either to:
<itemizedlist>
<listitem>Encrypted ZIP file on local filesystem</listitem>
<listitem>Directory on local filesystem</listitem>
<listitem>Single JSON file on your filesystem</listitem>
</itemizedlist>
When importing using the "dir" or "zip" strategies, note that the files need to follow the naming convention specified below.
When importing using the "dir" strategy, note that the files need to follow the naming convention specified below.
If you are importing files which were previously exported, the files already follow this convention.
<itemizedlist>
<listitem>{REALM_NAME}-realm.json, such as "acme-roadrunner-affairs-realm.json" for the realm named "acme-roadrunner-affairs"</listitem>
@ -21,26 +20,10 @@
</itemizedlist>
</para>
<para>
Encrypted ZIP is recommended as export contains many sensitive informations like passwords of your users (even if they are hashed),
but also their email addresses, and especially private keys of the realms. Directory and Single JSON file are useful especially
for testing as data in the files are not protected. On the other hand, it's useful if you want to look at all your data in JSON
files directly.
</para>
<para>
If you import to ZIP or Directory, you can specify also the number of users to be stored in each JSON file. So if you have
If you import to Directory, you can specify also the number of users to be stored in each JSON file. So if you have
very large amount of users in your database, you likely don't want to import them into single file as the file might be very big.
Processing of each file is done in separate transaction as exporting/importing all users at once could also lead to memory issues.
</para>
<para>
So to export the content of your Keycloak database into encrypted ZIP, you can execute Keycloak server with the System properties like:
<programlisting><![CDATA[
bin/standalone.sh -Dkeycloak.migration.action=export
-Dkeycloak.migration.provider=zip -Dkeycloak.migration.zipFile=<FILE TO EXPORT TO>
-Dkeycloak.migration.zipPassword=<PASSWORD TO DECRYPT EXPORT>
]]></programlisting>
Then you can move or copy the encrypted ZIP file into second environment and you can trigger import from it into Keycloak server with the same command but use
<literal>-Dkeycloak.migration.action=import</literal> instead of <literal>export</literal> .
</para>
<para>
To export into unencrypted directory you can use:
<programlisting><![CDATA[
@ -80,7 +63,7 @@ bin/standalone.sh -Dkeycloak.migration.action=import
<term>-Dkeycloak.migration.usersExportStrategy</term>
<listitem>
<para>
can be used to specify for ZIP or Directory providers to specify where to import users.
can be used to specify for Directory providers to specify where to import users.
Possible values are:
<itemizedlist>
<listitem>DIFFERENT_FILES - Users will be exported into more different files according to maximum number of users per file. This is default value</listitem>

View file

@ -1,67 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-export-import-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.6.0.Final-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-export-import-zip</artifactId>
<name>Keycloak Export Import To Encrypted ZIP</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,82 +0,0 @@
package org.keycloak.exportimport.zip;
import de.idyl.winzipaes.AesZipFileEncrypter;
import de.idyl.winzipaes.impl.AESEncrypter;
import de.idyl.winzipaes.impl.AESEncrypterBC;
import org.jboss.logging.Logger;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.exportimport.util.ExportUtils;
import org.keycloak.exportimport.util.MultipleStepsExportProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipExportProvider extends MultipleStepsExportProvider {
private static final Logger logger = Logger.getLogger(ZipExportProvider.class);
private final AesZipFileEncrypter encrypter;
private final String password;
public ZipExportProvider(File zipFile, String password) {
if (zipFile.exists()) {
throw new IllegalStateException("File " + zipFile.getAbsolutePath() + " already exists");
}
this.password = password;
try {
AESEncrypter encrypter = new AESEncrypterBC();
this.encrypter = new AesZipFileEncrypter(zipFile, encrypter);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
logger.infof("Exporting into zip file %s", zipFile.getAbsolutePath());
}
@Override
protected void writeRealm(String fileName, RealmRepresentation rep) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonSerialization.mapper.writeValue(stream, rep);
writeStream(fileName, stream);
}
@Override
protected void writeUsers(String fileName, KeycloakSession session, RealmModel realm, List<UserModel> users) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ExportUtils.exportUsersToStream(session, realm, users, JsonSerialization.mapper, stream);
writeStream(fileName, stream);
}
@Override
protected void writeVersion(String fileName, VersionRepresentation version) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonSerialization.mapper.writeValue(stream, version);
writeStream(fileName, stream);
}
private void writeStream(String fileName, ByteArrayOutputStream stream) throws IOException {
byte[] byteArray = stream.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
this.encrypter.add(fileName, bis, this.password);
}
@Override
public void close() {
try {
this.encrypter.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}

View file

@ -1,50 +0,0 @@
package org.keycloak.exportimport.zip;
import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.ExportProvider;
import org.keycloak.exportimport.ExportProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.io.File;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipExportProviderFactory implements ExportProviderFactory {
public static final String PROVIDER_ID = "zip";
@Override
public ExportProvider create(KeycloakSession session) {
String fileName = ExportImportConfig.getZipFile();
String password = ExportImportConfig.getZipPassword();
if (fileName == null) {
throw new IllegalArgumentException("ZIP file for export not provided");
}
if (password == null) {
throw new IllegalArgumentException("Password for encrypting ZIP not provided");
}
return new ZipExportProvider(new File(fileName), password);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -1,135 +0,0 @@
package org.keycloak.exportimport.zip;
import de.idyl.winzipaes.AesZipFileDecrypter;
import de.idyl.winzipaes.impl.AESDecrypter;
import de.idyl.winzipaes.impl.AESDecrypterBC;
import de.idyl.winzipaes.impl.ExtZipEntry;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.Strategy;
import org.keycloak.exportimport.util.ExportImportSessionTask;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipImportProvider implements ImportProvider {
private static final Logger logger = Logger.getLogger(ZipImportProvider.class);
private final AesZipFileDecrypter decrypter;
private final String password;
public ZipImportProvider(File zipFile, String password) {
try {
if (!zipFile.exists()) {
throw new IllegalStateException("File " + zipFile.getAbsolutePath() + " doesn't exists");
}
AESDecrypter decrypter = new AESDecrypterBC();
this.decrypter = new AesZipFileDecrypter(zipFile, decrypter);
this.password = password;
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
logger.infof("Importing from ZIP file %s", zipFile.getAbsolutePath());
}
@Override
public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException {
List<String> realmNames = getRealmsToImport();
for (String realmName : realmNames) {
importRealm(factory, realmName, strategy);
}
}
@Override
public boolean isMasterRealmExported() throws IOException {
List<String> realmNames = getRealmsToImport();
return realmNames.contains(Config.getAdminRealm());
}
private List<String> getRealmsToImport() throws IOException {
List<String> realmNames = new ArrayList<String>();
for (ExtZipEntry entry : this.decrypter.getEntryList()) {
String entryName = entry.getName();
if (entryName.endsWith("-realm.json")) {
// Parse "foo" from "foo-realm.json"
String realmName = entryName.substring(0, entryName.length() - 11);
// Ensure that master realm is imported first
if (Config.getAdminRealm().equals(realmName)) {
realmNames.add(0, realmName);
} else {
realmNames.add(realmName);
}
}
}
return realmNames;
}
@Override
public void importRealm(KeycloakSessionFactory factory, final String realmName, final Strategy strategy) throws IOException {
try {
// Import realm first
ByteArrayOutputStream bos = new ByteArrayOutputStream();
this.decrypter.extractEntry(this.decrypter.getEntry(realmName + "-realm.json"), bos, this.password);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
final RealmRepresentation realmRep = JsonSerialization.mapper.readValue(bis, RealmRepresentation.class);
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importRealm(session, realmRep, strategy);
}
});
// Import users
for (ExtZipEntry entry : this.decrypter.getEntryList()) {
String name = entry.getName();
if (name.matches(realmName + "-users-[0-9]+\\.json")) {
bos = new ByteArrayOutputStream();
this.decrypter.extractEntry(entry, bos, this.password);
final ByteArrayInputStream bis2 = new ByteArrayInputStream(bos.toByteArray());
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, bis2);
}
});
}
}
} catch (DataFormatException dfe) {
throw new RuntimeException(dfe);
}
}
@Override
public void close() {
try {
this.decrypter.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}

View file

@ -1,47 +0,0 @@
package org.keycloak.exportimport.zip;
import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.ImportProvider;
import org.keycloak.exportimport.ImportProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.io.File;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ZipImportProviderFactory implements ImportProviderFactory {
@Override
public ImportProvider create(KeycloakSession session) {
String fileName = ExportImportConfig.getZipFile();
String password = ExportImportConfig.getZipPassword();
if (fileName == null) {
throw new IllegalArgumentException("ZIP file for import not provided");
}
if (password == null) {
throw new IllegalArgumentException("Password for decrypting ZIP not provided");
}
return new ZipImportProvider(new File(fileName), password);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ZipExportProviderFactory.PROVIDER_ID;
}
}

View file

@ -1 +0,0 @@
org.keycloak.exportimport.zip.ZipExportProviderFactory

View file

@ -1 +0,0 @@
org.keycloak.exportimport.zip.ZipImportProviderFactory

View file

@ -18,7 +18,6 @@
<module>export-import-api</module>
<module>export-import-dir</module>
<module>export-import-single-file</module>
<module>export-import-zip</module>
</modules>
</project>

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

@ -90,14 +90,7 @@ public class Pbkdf2PasswordEncoder {
public static byte[] getSalt() {
byte[] buffer = new byte[16];
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstance(RNG_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("RNG algorithm not found");
}
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(buffer);
return buffer;

13
pom.xml
View file

@ -58,7 +58,6 @@
<servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
<google.zxing.version>3.2.1</google.zxing.version>
<github.relaxng.version>2011.1</github.relaxng.version>
<winzipaes.version>1.0.1</winzipaes.version>
<freemarker.version>2.3.23</freemarker.version>
<twitter4j.version>4.0.4</twitter4j.version>
<selenium.version>2.35.0</selenium.version>
@ -373,13 +372,6 @@
<scope>test</scope>
</dependency>
<!-- Encrypted ZIP -->
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<version>${winzipaes.version}</version>
</dependency>
<!-- Apache DS -->
<dependency>
<groupId>org.apache.directory.server</groupId>
@ -700,11 +692,6 @@
<artifactId>keycloak-export-import-single-file</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-zip</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>

View file

@ -18,10 +18,7 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -116,6 +113,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
private class ValidationContextImpl extends FormContextImpl implements ValidationContext {
FormAction action;
String error;
private ValidationContextImpl(AuthenticationExecutionModel executionModel, FormAction action) {
super(executionModel);
@ -131,6 +129,10 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
this.formData = formData;
}
public void error(String error) {
this.error = error;
}
@Override
public void success() {
success = true;
@ -145,6 +147,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
List<FormAction> requiredActions = new LinkedList<>();
List<ValidationContextImpl> successes = new LinkedList<>();
List<ValidationContextImpl> errors = new LinkedList<>();
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
if (!formActionExecution.isEnabled()) {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
@ -183,12 +186,28 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
successes.add(result);
} else {
processor.logFailure();
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return renderForm(result.formData, result.errors);
errors.add(result);
}
}
if (!errors.isEmpty()) {
processor.logFailure();
List<FormMessage> messages = new LinkedList<>();
Set<String> fields = new HashSet<>();
for (ValidationContextImpl v : errors) {
for (FormMessage m : v.errors) {
if (!fields.contains(m.getField())) {
fields.add(m.getField());
messages.add(m);
}
}
}
ValidationContextImpl first = errors.get(0);
first.getEvent().error(first.error);
return renderForm(first.formData, messages);
}
for (ValidationContextImpl context : successes) {
context.action.success(context);
}

View file

@ -21,6 +21,8 @@ public interface ValidationContext extends FormContext {
*/
void validationError(MultivaluedMap<String, String> formData, List<FormMessage> errors);
void error(String error);
/**
* Mark this validation as sucessful
*

View file

@ -59,7 +59,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
}
if (errors.size() > 0) {
context.getEvent().error(Errors.INVALID_REGISTRATION);
context.error(Errors.INVALID_REGISTRATION);
formData.remove(RegistrationPage.FIELD_PASSWORD);
formData.remove(RegistrationPage.FIELD_PASSWORD_CONFIRM);
context.validationError(formData, errors);

View file

@ -56,15 +56,17 @@ public class RegistrationProfile implements FormAction, FormActionFactory {
}
String email = formData.getFirst(Validation.FIELD_EMAIL);
boolean emailValid = true;
if (Validation.isBlank(email)) {
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));
emailValid = false;
} else if (!Validation.isEmailValid(email)) {
formData.remove(Validation.FIELD_EMAIL);
context.getEvent().detail(Details.EMAIL, email);
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
emailValid = false;
}
if (context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
if (emailValid && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
eventError = Errors.EMAIL_IN_USE;
formData.remove(Validation.FIELD_EMAIL);
context.getEvent().detail(Details.EMAIL, email);
@ -72,7 +74,7 @@ public class RegistrationProfile implements FormAction, FormActionFactory {
}
if (errors.size() > 0) {
context.getEvent().error(eventError);
context.error(eventError);
context.validationError(formData, errors);
return;

View file

@ -108,7 +108,7 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory, Con
} else {
errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));
formData.remove(G_RECAPTCHA_RESPONSE);
context.getEvent().error(Errors.INVALID_REGISTRATION);
context.error(Errors.INVALID_REGISTRATION);
context.validationError(formData, errors);
return;

View file

@ -56,9 +56,8 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
String usernameField = RegistrationPage.FIELD_USERNAME;
if (context.getRealm().isRegistrationEmailAsUsername()) {
username = email;
context.getEvent().detail(Details.USERNAME, username);
usernameField = RegistrationPage.FIELD_EMAIL;
context.getEvent().detail(Details.USERNAME, email);
if (Validation.isBlank(email)) {
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.MISSING_EMAIL));
} else if (!Validation.isEmailValid(email)) {
@ -66,33 +65,32 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
formData.remove(Validation.FIELD_EMAIL);
}
if (errors.size() > 0) {
context.getEvent().error(Errors.INVALID_REGISTRATION);
context.error(Errors.INVALID_REGISTRATION);
context.validationError(formData, errors);
return;
}
if (email != null && context.getSession().users().getUserByEmail(email, context.getRealm()) != null) {
context.getEvent().error(Errors.USERNAME_IN_USE);
context.error(Errors.EMAIL_IN_USE);
formData.remove(Validation.FIELD_EMAIL);
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.USERNAME_EXISTS));
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.EMAIL_EXISTS));
context.validationError(formData, errors);
return;
}
} else {
if (Validation.isBlank(username)) {
context.getEvent().error(Errors.INVALID_REGISTRATION);
context.error(Errors.INVALID_REGISTRATION);
errors.add(new FormMessage(RegistrationPage.FIELD_USERNAME, Messages.MISSING_USERNAME));
context.validationError(formData, errors);
return;
}
}
if (context.getSession().users().getUserByUsername(username, context.getRealm()) != null) {
context.getEvent().error(Errors.USERNAME_IN_USE);
context.error(Errors.USERNAME_IN_USE);
errors.add(new FormMessage(usernameField, Messages.USERNAME_EXISTS));
formData.remove(Validation.FIELD_USERNAME);
formData.remove(Validation.FIELD_EMAIL);
context.validationError(formData, errors);
return;
}
}
context.success();

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.authentication;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.authentication.PasswordPolicy;
@ -30,7 +31,7 @@ import static org.keycloak.testsuite.console.page.authentication.PasswordPolicy.
* @author Petr Mensik
* @author mhajas
*/
//@Ignore // FIXME still unstable
@Ignore // FIXME still unstable
public class PasswordPolicyTest extends AbstractConsoleTest {
@Page

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.realm;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.testsuite.console.page.realm.TokenSettings;
@ -30,6 +31,7 @@ import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
*
* @author Petr Mensik
*/
@Ignore
public class TokensTest extends AbstractRealmTest {
@Page

View file

@ -10,7 +10,6 @@ import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.dir.DirExportProvider;
import org.keycloak.exportimport.dir.DirExportProviderFactory;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.exportimport.zip.ZipExportProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
@ -217,30 +216,6 @@ public class ExportImportTest {
}
}
@Test
public void testZipFullExportImport() throws Throwable {
ExportImportConfig.setProvider(ZipExportProviderFactory.PROVIDER_ID);
String zipFilePath = getExportImportTestDirectory() + File.separator + "export-full.zip";
new File(zipFilePath).delete();
ExportImportConfig.setZipFile(zipFilePath);
ExportImportConfig.setZipPassword("encPassword");
ExportImportConfig.setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE);
testFullExportImport();
}
@Test
public void testZipRealmExportImport() throws Throwable {
ExportImportConfig.setProvider(ZipExportProviderFactory.PROVIDER_ID);
String zipFilePath = getExportImportTestDirectory() + File.separator + "export-realm.zip";
new File(zipFilePath).delete();
ExportImportConfig.setZipFile(zipFilePath);
ExportImportConfig.setZipPassword("encPassword");
ExportImportConfig.setUsersPerFile(3);
testRealmExportImport();
}
private void testFullExportImport() {
ExportImportConfig.setAction(ExportImportConfig.ACTION_EXPORT);
ExportImportConfig.setRealmName(null);

View file

@ -21,10 +21,7 @@
*/
package org.keycloak.testsuite.forms;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.*;
import org.keycloak.events.Details;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
@ -42,6 +39,8 @@ import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -80,15 +79,15 @@ public class RegisterTest {
registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Username already exists.", registerPage.getError());
assertEquals("Username already exists.", registerPage.getError());
// assert form keeps form fields on error
Assert.assertEquals("firstName", registerPage.getFirstName());
Assert.assertEquals("lastName", registerPage.getLastName());
Assert.assertEquals("", registerPage.getEmail());
Assert.assertEquals("", registerPage.getUsername());
Assert.assertEquals("", registerPage.getPassword());
Assert.assertEquals("", registerPage.getPasswordConfirm());
assertEquals("firstName", registerPage.getFirstName());
assertEquals("lastName", registerPage.getLastName());
assertEquals("registerExistingUser@email", registerPage.getEmail());
assertEquals("", registerPage.getUsername());
assertEquals("", registerPage.getPassword());
assertEquals("", registerPage.getPasswordConfirm());
events.expectRegister("test-user@localhost", "registerExistingUser@email")
.removeDetail(Details.EMAIL)
@ -104,15 +103,15 @@ public class RegisterTest {
registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirm@email", "registerUserInvalidPasswordConfirm", "password", "invalid");
registerPage.assertCurrent();
Assert.assertEquals("Password confirmation doesn't match.", registerPage.getError());
assertEquals("Password confirmation doesn't match.", registerPage.getError());
// assert form keeps form fields on error
Assert.assertEquals("firstName", registerPage.getFirstName());
Assert.assertEquals("lastName", registerPage.getLastName());
Assert.assertEquals("registerUserInvalidPasswordConfirm@email", registerPage.getEmail());
Assert.assertEquals("registerUserInvalidPasswordConfirm", registerPage.getUsername());
Assert.assertEquals("", registerPage.getPassword());
Assert.assertEquals("", registerPage.getPasswordConfirm());
assertEquals("firstName", registerPage.getFirstName());
assertEquals("lastName", registerPage.getLastName());
assertEquals("registerUserInvalidPasswordConfirm@email", registerPage.getEmail());
assertEquals("registerUserInvalidPasswordConfirm", registerPage.getUsername());
assertEquals("", registerPage.getPassword());
assertEquals("", registerPage.getPasswordConfirm());
events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email")
.removeDetail(Details.USERNAME)
@ -129,7 +128,7 @@ public class RegisterTest {
registerPage.register("firstName", "lastName", "registerUserMissingPassword@email", "registerUserMissingPassword", null, null);
registerPage.assertCurrent();
Assert.assertEquals("Please specify password.", registerPage.getError());
assertEquals("Please specify password.", registerPage.getError());
events.expectRegister("registerUserMissingPassword", "registerUserMissingPassword@email")
.removeDetail(Details.USERNAME)
@ -154,7 +153,7 @@ public class RegisterTest {
registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "pass", "pass");
registerPage.assertCurrent();
Assert.assertEquals("Invalid password: minimum length 8.", registerPage.getError());
assertEquals("Invalid password: minimum length 8.", registerPage.getError());
events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email")
.removeDetail(Details.USERNAME)
@ -162,7 +161,7 @@ public class RegisterTest {
.user((String) null).error("invalid_registration").assertEvent();
registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "password", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email").assertEvent().getUserId();
@ -186,7 +185,7 @@ public class RegisterTest {
registerPage.register("firstName", "lastName", "registerUserMissingUsername@email", null, "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Please specify username.", registerPage.getError());
assertEquals("Please specify username.", registerPage.getError());
events.expectRegister(null, "registerUserMissingUsername@email")
.removeDetail(Details.USERNAME)
@ -195,21 +194,51 @@ public class RegisterTest {
}
@Test
public void registerUserMissingOrInvalidEmail() {
public void registerUserManyErrors() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register(null, null, null, null, null, null);
registerPage.assertCurrent();
assertEquals("Please specify username.\n" +
"Please specify first name.\n" +
"Please specify last name.\n" +
"Please specify email.\n" +
"Please specify password.", registerPage.getError());
events.expectRegister(null, "registerUserMissingUsername@email")
.removeDetail(Details.USERNAME)
.removeDetail(Details.EMAIL)
.error("invalid_registration").assertEvent();
}
@Test
public void registerUserMissingEmail() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Please specify email.", registerPage.getError());
assertEquals("Please specify email.", registerPage.getError());
events.expectRegister("registerUserMissingEmail", null)
.removeDetail("email")
.error("invalid_registration").assertEvent();
}
@Test
public void registerUserInvalidEmail() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "registerUserInvalidEmailemail", "registerUserInvalidEmail", "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Invalid email address.", registerPage.getError());
assertEquals("registerUserInvalidEmailemail", registerPage.getEmail());
assertEquals("Invalid email address.", registerPage.getError());
events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail")
.error("invalid_registration").assertEvent();
}
@ -222,7 +251,7 @@ public class RegisterTest {
registerPage.register("firstName", "lastName", "registerUserSuccess@email", "registerUserSuccess", "password", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerusersuccess").user(userId).assertEvent();
@ -233,10 +262,10 @@ public class RegisterTest {
// test that timestamp is current with 10s tollerance
Assert.assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
// test user info is set from form
Assert.assertEquals("registerusersuccess", user.getUsername());
Assert.assertEquals("registerusersuccess@email", user.getEmail());
Assert.assertEquals("firstName", user.getFirstName());
Assert.assertEquals("lastName", user.getLastName());
assertEquals("registerusersuccess", user.getUsername());
assertEquals("registerusersuccess@email", user.getEmail());
assertEquals("firstName", user.getFirstName());
assertEquals("lastName", user.getLastName());
}
protected UserModel getUser(String userId) {
@ -261,9 +290,9 @@ public class RegisterTest {
registerPage.registerWithEmailAsUsername("firstName", "lastName", "test-user@localhost", "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Username already exists.", registerPage.getError());
assertEquals("Email already exists.", registerPage.getError());
events.expectRegister("test-user@localhost", "test-user@localhost").user((String) null).error("username_in_use").assertEvent();
events.expectRegister("test-user@localhost", "test-user@localhost").user((String) null).error("email_in_use").assertEvent();
} finally {
configureRelamRegistrationEmailAsUsername(false);
}
@ -280,12 +309,12 @@ public class RegisterTest {
registerPage.registerWithEmailAsUsername("firstName", "lastName", null, "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Please specify email.", registerPage.getError());
assertEquals("Please specify email.", registerPage.getError());
events.expectRegister(null, null).removeDetail("username").removeDetail("email").error("invalid_registration").assertEvent();
registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserInvalidEmailemail", "password", "password");
registerPage.assertCurrent();
Assert.assertEquals("Invalid email address.", registerPage.getError());
assertEquals("Invalid email address.", registerPage.getError());
events.expectRegister("registerUserInvalidEmailemail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent();
} finally {
configureRelamRegistrationEmailAsUsername(false);
@ -303,7 +332,7 @@ public class RegisterTest {
registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserSuccessE@email", "password", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerusersuccesse@email").user(userId).assertEvent();