Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
666851a44a
15 changed files with 102 additions and 57 deletions
|
@ -13,14 +13,6 @@
|
|||
<name>Keycloak Wildfly Adapter Distro</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
|
|
@ -12,6 +12,13 @@
|
|||
<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.
|
||||
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>
|
||||
<listitem>{REALM_NAME}-users-{INDEX}.json, such as "acme-roadrunner-affairs-users-0.json" for the first users file of the realm named "acme-roadrunner-affairs"</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
Encrypted ZIP is recommended as export contains many sensitive informations like passwords of your users (even if they are hashed),
|
||||
|
@ -111,5 +118,14 @@ bin/standalone.sh -Dkeycloak.migration.action=import
|
|||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
<para>
|
||||
When importing realm files that weren't exported before, the option <literal>keycloak.import</literal> can be used. If more than one realm
|
||||
file needs to be imported, a comma separated list of file names can be specified. This is more appropriate than the cases before, as this
|
||||
will happen only after the master realm has been initialized. Examples:
|
||||
<itemizedlist>
|
||||
<listitem>-Dkeycloak.import=/tmp/realm1.json</listitem>
|
||||
<listitem>-Dkeycloak.import=/tmp/realm1.json,/tmp/realm2.json</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
</chapter>
|
|
@ -60,6 +60,13 @@
|
|||
</modules>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -45,7 +45,7 @@ import java.util.Set;
|
|||
public class ExportUtils {
|
||||
|
||||
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, boolean includeUsers) {
|
||||
RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm);
|
||||
RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, true);
|
||||
|
||||
// Audit
|
||||
rep.setEventsEnabled(realm.isEventsEnabled());
|
||||
|
|
|
@ -63,35 +63,37 @@
|
|||
<span tooltip-placement="right" tooltip="Should newly created users be created within LDAP store? Priority effects which provider is chose to sync the new user." class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="vendor">Vendor</label>
|
||||
<label class="col-sm-2 control-label" for="vendor">Vendor<span class="required">*</span></label>
|
||||
<div class="col-sm-4">
|
||||
<div class="select-kc">
|
||||
<select id="vendor"
|
||||
ng-model="instance.config.vendor"
|
||||
ng-options="vendor.id as vendor.name for vendor in ldapVendors">
|
||||
ng-options="vendor.id as vendor.name for vendor in ldapVendors"
|
||||
required>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="LDAP vendor (provider)" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="usernameLDAPAttribute">Username LDAP attribute </label>
|
||||
<label class="col-sm-2 control-label" for="usernameLDAPAttribute">Username LDAP attribute<span class="required">*</span></label>
|
||||
<div class="col-sm-4">
|
||||
<div class="select-kc">
|
||||
<select id="usernameLDAPAttribute"
|
||||
ng-model="instance.config.usernameLDAPAttribute"
|
||||
ng-options="usernameLDAPAttribute for usernameLDAPAttribute in usernameLDAPAttributes">
|
||||
ng-options="usernameLDAPAttribute for usernameLDAPAttribute in usernameLDAPAttributes"
|
||||
required>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="Name of LDAP attribute, which is mapped as Keycloak username" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="userObjectClasses">User Object Classes </label>
|
||||
<label class="col-sm-2 control-label" for="userObjectClasses">User Object Classes<span class="required">*</span></label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" id="userObjectClasses" type="text" ng-model="instance.config.userObjectClasses" placeholder="LDAP User Object Classes (div. by comma)">
|
||||
<input class="form-control" id="userObjectClasses" type="text" ng-model="instance.config.userObjectClasses" placeholder="LDAP User Object Classes (div. by comma)" required>
|
||||
</div>
|
||||
<span tooltip-placement="right" tooltip="All values of LDAP objectClass attribute divided by comma, which are used for newly created LDAP users" class="fa fa-info-circle"></span>
|
||||
<span tooltip-placement="right" tooltip="All values of LDAP objectClass attribute for users in LDAP divided by comma" class="fa fa-info-circle"></span>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-sm-2 control-label" for="ldapConnectionUrl">Connection URL<span class="required">*</span></label>
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
<div id="content">
|
||||
<h2><span>{{realm.realm}}</span> Realm Public Key <span tooltip-placement="right" tooltip="Realm's public key. This is used to verify any signed tokens or documents created by the realm." class="fa fa-info-circle"></span></h2>
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
|
||||
<button class="btn btn-primary btn-lg" type="submit" data-ng-click="generate()">Generate new keys</button>
|
||||
</div>
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="publicKey">Public key</label>
|
||||
|
@ -18,7 +15,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="publicKey">Certificate</label>
|
||||
<label class="col-sm-2 control-label" for="certificate">Certificate</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<textarea type="text" id="certificate" name="certificate" class="form-control" rows="5"
|
||||
|
@ -26,6 +23,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
|
||||
<button class="btn btn-primary btn-lg" type="submit" data-ng-click="generate()">Generate new keys</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -132,7 +132,7 @@ public class PasswordPolicy {
|
|||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + count + " numerical digits" : null;
|
||||
return count < min ? "Invalid password: must contain at least " + min + " numerical digits" : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ public class PasswordPolicy {
|
|||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + count + " lower case characters": null;
|
||||
return count < min ? "Invalid password: must contain at least " + min + " lower case characters": null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ public class PasswordPolicy {
|
|||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + count + " upper case characters" : null;
|
||||
return count < min ? "Invalid password: must contain at least " + min + " upper case characters" : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ public class PasswordPolicy {
|
|||
count++;
|
||||
}
|
||||
}
|
||||
return count < min ? "Invalid password: must contain at least " + count + " special characters" : null;
|
||||
return count < min ? "Invalid password: must contain at least " + min + " special characters" : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ public class ModelToRepresentation {
|
|||
return rep;
|
||||
}
|
||||
|
||||
public static RealmRepresentation toRepresentation(RealmModel realm) {
|
||||
public static RealmRepresentation toRepresentation(RealmModel realm, boolean internal) {
|
||||
RealmRepresentation rep = new RealmRepresentation();
|
||||
rep.setId(realm.getId());
|
||||
rep.setRealm(realm.getName());
|
||||
|
@ -85,13 +85,15 @@ public class ModelToRepresentation {
|
|||
rep.setUpdateProfileOnInitialSocialLogin(realm.isUpdateProfileOnInitialSocialLogin());
|
||||
rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
|
||||
rep.setPublicKey(realm.getPublicKeyPem());
|
||||
rep.setPrivateKey(realm.getPrivateKeyPem());
|
||||
String privateKeyPem = realm.getPrivateKeyPem();
|
||||
if (realm.getCertificatePem() == null && privateKeyPem != null) {
|
||||
KeycloakModelUtils.generateRealmCertificate(realm);
|
||||
if (internal) {
|
||||
rep.setPrivateKey(realm.getPrivateKeyPem());
|
||||
String privateKeyPem = realm.getPrivateKeyPem();
|
||||
if (realm.getCertificatePem() == null && privateKeyPem != null) {
|
||||
KeycloakModelUtils.generateRealmCertificate(realm);
|
||||
}
|
||||
rep.setCodeSecret(realm.getCodeSecret());
|
||||
}
|
||||
rep.setCertificate(realm.getCertificatePem());
|
||||
rep.setCodeSecret(realm.getCodeSecret());
|
||||
rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
|
||||
rep.setRegistrationAllowed(realm.isRegistrationAllowed());
|
||||
rep.setRememberMe(realm.isRememberMe());
|
||||
|
|
|
@ -11,55 +11,55 @@ public class PasswordPolicyTest {
|
|||
@Test
|
||||
public void testLength() {
|
||||
PasswordPolicy policy = new PasswordPolicy("length");
|
||||
Assert.assertNotNull(policy.validate("1234567"));
|
||||
Assert.assertEquals("Invalid password: minimum length 8", policy.validate("1234567"));
|
||||
Assert.assertNull(policy.validate("12345678"));
|
||||
|
||||
policy = new PasswordPolicy("length(4)");
|
||||
Assert.assertNotNull(policy.validate("123"));
|
||||
Assert.assertEquals("Invalid password: minimum length 4", policy.validate("123"));
|
||||
Assert.assertNull(policy.validate("1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigits() {
|
||||
PasswordPolicy policy = new PasswordPolicy("digits");
|
||||
Assert.assertNotNull(policy.validate("abcd"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("abcd"));
|
||||
Assert.assertNull(policy.validate("abcd1"));
|
||||
|
||||
policy = new PasswordPolicy("digits(2)");
|
||||
Assert.assertNotNull(policy.validate("abcd1"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("abcd1"));
|
||||
Assert.assertNull(policy.validate("abcd12"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerCase() {
|
||||
PasswordPolicy policy = new PasswordPolicy("lowerCase");
|
||||
Assert.assertNotNull(policy.validate("ABCD1234"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("ABCD1234"));
|
||||
Assert.assertNull(policy.validate("ABcD1234"));
|
||||
|
||||
policy = new PasswordPolicy("lowerCase(2)");
|
||||
Assert.assertNotNull(policy.validate("ABcD1234"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("ABcD1234"));
|
||||
Assert.assertNull(policy.validate("aBcD1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperCase() {
|
||||
PasswordPolicy policy = new PasswordPolicy("upperCase");
|
||||
Assert.assertNotNull(policy.validate("abcd1234"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("abcd1234"));
|
||||
Assert.assertNull(policy.validate("abCd1234"));
|
||||
|
||||
policy = new PasswordPolicy("upperCase(2)");
|
||||
Assert.assertNotNull(policy.validate("abCd1234"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("abCd1234"));
|
||||
Assert.assertNull(policy.validate("AbCd1234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecialChars() {
|
||||
PasswordPolicy policy = new PasswordPolicy("specialChars");
|
||||
Assert.assertNotNull(policy.validate("abcd1234"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("abcd1234"));
|
||||
Assert.assertNull(policy.validate("ab&d1234"));
|
||||
|
||||
policy = new PasswordPolicy("specialChars(2)");
|
||||
Assert.assertNotNull(policy.validate("ab&d1234"));
|
||||
Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("ab&d1234"));
|
||||
Assert.assertNull(policy.validate("ab&d-234"));
|
||||
}
|
||||
|
||||
|
|
|
@ -194,15 +194,19 @@ public class KeycloakApplication extends Application {
|
|||
}
|
||||
|
||||
public void importRealmFile() {
|
||||
String file = System.getProperty("keycloak.import");
|
||||
if (file != null) {
|
||||
RealmRepresentation rep = null;
|
||||
try {
|
||||
rep = loadJson(new FileInputStream(file), RealmRepresentation.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
String files = System.getProperty("keycloak.import");
|
||||
if (files != null) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(files, ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String file = tokenizer.nextToken().trim();
|
||||
RealmRepresentation rep = null;
|
||||
try {
|
||||
rep = loadJson(new FileInputStream(file), RealmRepresentation.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
importRealm(rep, "file " + file);
|
||||
}
|
||||
importRealm(rep, "file " + file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,11 +227,14 @@ public class KeycloakApplication extends Application {
|
|||
return;
|
||||
}
|
||||
|
||||
RealmModel realm = manager.importRealm(rep);
|
||||
|
||||
log.info("Imported realm " + realm.getName() + " from " + from);
|
||||
|
||||
session.getTransaction().commit();
|
||||
try {
|
||||
RealmModel realm = manager.importRealm(rep);
|
||||
session.getTransaction().commit();
|
||||
log.info("Imported realm " + realm.getName() + " from " + from);
|
||||
} catch (Throwable t) {
|
||||
session.getTransaction().rollback();
|
||||
log.warn("Unable to import realm " + rep.getRealm() + " from " + from + ". Cause: " + t.getMessage());
|
||||
}
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ public class RealmAdminResource {
|
|||
@Produces("application/json")
|
||||
public RealmRepresentation getRealm() {
|
||||
if (auth.hasView()) {
|
||||
RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm);
|
||||
RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, false);
|
||||
if (session.realms() instanceof CacheRealmProvider) {
|
||||
CacheRealmProvider cacheRealmProvider = (CacheRealmProvider)session.realms();
|
||||
rep.setRealmCacheEnabled(cacheRealmProvider.isEnabled());
|
||||
|
|
|
@ -100,7 +100,7 @@ public class RealmsAdminResource {
|
|||
|
||||
protected void addRealmRep(List<RealmRepresentation> reps, RealmModel realm, ApplicationModel realmManagementApplication) {
|
||||
if (auth.hasAppRole(realmManagementApplication, AdminRoles.MANAGE_REALM)) {
|
||||
reps.add(ModelToRepresentation.toRepresentation(realm));
|
||||
reps.add(ModelToRepresentation.toRepresentation(realm, false));
|
||||
} else if (auth.hasOneOfAppRole(realmManagementApplication, AdminRoles.ALL_REALM_ROLES)) {
|
||||
RealmRepresentation rep = new RealmRepresentation();
|
||||
rep.setRealm(realm.getName());
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.keycloak.admin.client.Keycloak;
|
|||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -42,6 +43,7 @@ public abstract class AbstractClientTest {
|
|||
|
||||
RealmModel testRealm = manager.createRealm(REALM_NAME);
|
||||
testRealm.setEnabled(true);
|
||||
KeycloakModelUtils.generateRealmKeys(testRealm);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,11 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -16,7 +20,15 @@ public class RealmTest extends AbstractClientTest {
|
|||
|
||||
@Test
|
||||
public void getRealms() {
|
||||
assertNames(keycloak.realms().findAll(), "master", "test", REALM_NAME);
|
||||
List<RealmRepresentation> realms = keycloak.realms().findAll();
|
||||
assertNames(realms, "master", "test", REALM_NAME);
|
||||
|
||||
for (RealmRepresentation rep : realms) {
|
||||
assertNull(rep.getPrivateKey());
|
||||
assertNull(rep.getCodeSecret());
|
||||
assertNotNull(rep.getPublicKey());
|
||||
assertNotNull(rep.getCertificate());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -65,6 +77,11 @@ public class RealmTest extends AbstractClientTest {
|
|||
RealmRepresentation rep = realm.toRepresentation();
|
||||
assertEquals(REALM_NAME, rep.getRealm());
|
||||
assertTrue(rep.isEnabled());
|
||||
|
||||
assertNull(rep.getPrivateKey());
|
||||
assertNull(rep.getCodeSecret());
|
||||
assertNotNull(rep.getPublicKey());
|
||||
assertNotNull(rep.getCertificate());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ public class ModelTest extends AbstractModelTest {
|
|||
}
|
||||
|
||||
private RealmModel importExport(RealmModel src, String copyName) {
|
||||
RealmRepresentation representation = ModelToRepresentation.toRepresentation(src);
|
||||
RealmRepresentation representation = ModelToRepresentation.toRepresentation(src, true);
|
||||
representation.setRealm(copyName);
|
||||
representation.setId(copyName);
|
||||
RealmModel copy = realmManager.importRealm(representation);
|
||||
|
|
Loading…
Reference in a new issue