KEYCLOAK-3494 Input elements backed by user attributes fail to update in themes
This commit is contained in:
parent
7912bd4dae
commit
5a015a6518
24 changed files with 669 additions and 304 deletions
|
@ -125,29 +125,32 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSingleAttribute(String name, String value) {
|
public void setSingleAttribute(String name, String value) {
|
||||||
boolean found = false;
|
String firstExistingAttrId = null;
|
||||||
List<UserAttributeEntity> toRemove = new ArrayList<>();
|
List<UserAttributeEntity> toRemove = new ArrayList<>();
|
||||||
for (UserAttributeEntity attr : user.getAttributes()) {
|
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||||
if (attr.getName().equals(name)) {
|
if (attr.getName().equals(name)) {
|
||||||
if (!found) {
|
if (firstExistingAttrId == null) {
|
||||||
attr.setValue(value);
|
attr.setValue(value);
|
||||||
found = true;
|
firstExistingAttrId = attr.getId();
|
||||||
} else {
|
} else {
|
||||||
toRemove.add(attr);
|
toRemove.add(attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (UserAttributeEntity attr : toRemove) {
|
if (firstExistingAttrId != null) {
|
||||||
em.remove(attr);
|
// Remove attributes through HQL to avoid StaleUpdateException
|
||||||
user.getAttributes().remove(attr);
|
Query query = em.createNamedQuery("deleteUserAttributesOtherThan");
|
||||||
}
|
query.setParameter("attrId", firstExistingAttrId);
|
||||||
|
query.setParameter("userId", user.getId());
|
||||||
|
int numUpdated = query.executeUpdate();
|
||||||
|
|
||||||
if (found) {
|
// Remove attribute from local entity
|
||||||
return;
|
user.getAttributes().removeAll(toRemove);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
persistAttributeValue(name, value);
|
persistAttributeValue(name, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -178,6 +181,15 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
|
||||||
query.setParameter("name", name);
|
query.setParameter("name", name);
|
||||||
query.setParameter("userId", user.getId());
|
query.setParameter("userId", user.getId());
|
||||||
int numUpdated = query.executeUpdate();
|
int numUpdated = query.executeUpdate();
|
||||||
|
|
||||||
|
// KEYCLOAK-3494 : Also remove attributes from local user entity
|
||||||
|
List<UserAttributeEntity> toRemove = new ArrayList<>();
|
||||||
|
for (UserAttributeEntity attr : user.getAttributes()) {
|
||||||
|
if (attr.getName().equals(name)) {
|
||||||
|
toRemove.add(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user.getAttributes().removeAll(toRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -43,6 +43,7 @@ import java.util.Set;
|
||||||
@NamedQuery(name="getAttributesByNameAndValue", query="select attr from UserAttributeEntity attr where attr.name = :name and attr.value = :value"),
|
@NamedQuery(name="getAttributesByNameAndValue", query="select attr from UserAttributeEntity attr where attr.name = :name and attr.value = :value"),
|
||||||
@NamedQuery(name="deleteUserAttributesByRealm", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId)"),
|
@NamedQuery(name="deleteUserAttributesByRealm", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId)"),
|
||||||
@NamedQuery(name="deleteUserAttributesByNameAndUser", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.name = :name"),
|
@NamedQuery(name="deleteUserAttributesByNameAndUser", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.name = :name"),
|
||||||
|
@NamedQuery(name="deleteUserAttributesOtherThan", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.id <> :attrId"),
|
||||||
@NamedQuery(name="deleteUserAttributesByRealmAndLink", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
|
@NamedQuery(name="deleteUserAttributesByRealmAndLink", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
|
||||||
})
|
})
|
||||||
@Table(name="USER_ATTRIBUTE")
|
@Table(name="USER_ATTRIBUTE")
|
||||||
|
|
|
@ -50,5 +50,5 @@ public interface Constants {
|
||||||
String KEY = "key";
|
String KEY = "key";
|
||||||
|
|
||||||
// Prefix for user attributes used in various "context"data maps
|
// Prefix for user attributes used in various "context"data maps
|
||||||
public static final String USER_ATTRIBUTES_PREFIX = "user.attributes.";
|
String USER_ATTRIBUTES_PREFIX = "user.attributes.";
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.forms.account.freemarker.model;
|
package org.keycloak.forms.account.freemarker.model;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -55,8 +56,8 @@ public class AccountBean {
|
||||||
|
|
||||||
if (profileFormData != null) {
|
if (profileFormData != null) {
|
||||||
for (String key : profileFormData.keySet()) {
|
for (String key : profileFormData.keySet()) {
|
||||||
if (key.startsWith("user.attributes.")) {
|
if (key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
|
||||||
String attribute = key.substring("user.attributes.".length());
|
String attribute = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
|
||||||
attributes.put(attribute, profileFormData.getFirst(key));
|
attributes.put(attribute, profileFormData.getFirst(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,11 @@
|
||||||
</provider>
|
</provider>
|
||||||
</spi>
|
</spi>
|
||||||
</xsl:variable>
|
</xsl:variable>
|
||||||
|
<xsl:variable name="themeModuleDefinition">
|
||||||
|
<modules>
|
||||||
|
<module>org.keycloak.testsuite.integration-arquillian-testsuite-providers</module>
|
||||||
|
</modules>
|
||||||
|
</xsl:variable>
|
||||||
|
|
||||||
<!--inject provider-->
|
<!--inject provider-->
|
||||||
<xsl:template match="//*[local-name()='providers']/*[local-name()='provider']">
|
<xsl:template match="//*[local-name()='providers']/*[local-name()='provider']">
|
||||||
|
@ -46,6 +51,14 @@
|
||||||
<xsl:text>module:org.keycloak.testsuite.integration-arquillian-testsuite-providers</xsl:text>
|
<xsl:text>module:org.keycloak.testsuite.integration-arquillian-testsuite-providers</xsl:text>
|
||||||
</provider>
|
</provider>
|
||||||
</xsl:template>
|
</xsl:template>
|
||||||
|
|
||||||
|
<!--inject provider for themes -->
|
||||||
|
<xsl:template match="//*[local-name()='theme']">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="@*|node()" />
|
||||||
|
<xsl:copy-of select="$themeModuleDefinition"/>
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
<!--inject truststore-->
|
<!--inject truststore-->
|
||||||
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsKS)]">
|
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsKS)]">
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"themes": [{
|
||||||
|
"name" : "address",
|
||||||
|
"types": [ "admin", "account", "login" ]
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.mainLayout active='account' bodyClass='user'; section>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<h2>${msg("editAccountHtmlTtile")}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 subtitle">
|
||||||
|
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="${url.accountUrl}" class="form-horizontal" method="post">
|
||||||
|
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="username" class="control-label">${msg("username")}</label> <#if realm.editUsernameAllowed><span class="required">*</span></#if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="username" name="username" <#if !realm.editUsernameAllowed>disabled="disabled"</#if> value="${(account.username!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="email" name="email" autofocus value="${(account.email!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="firstName" name="firstName" value="${(account.firstName!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="lastName" name="lastName" value="${(account.lastName!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="user.attributes.street" class="control-label">${msg("street")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="user.attributes.street" name="user.attributes.street" value="${(account.attributes.street!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="user.attributes.locality" class="control-label">${msg("locality")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="user.attributes.locality" name="user.attributes.locality" value="${(account.attributes.locality!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="user.attributes.region" class="control-label">${msg("region")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="user.attributes.region" name="user.attributes.region" value="${(account.attributes.region!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="user.attributes.postal_code" class="control-label">${msg("postal_code")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(account.attributes.postal_code!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="user.attributes.country" class="control-label">${msg("country")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="user.attributes.country" name="user.attributes.country" value="${(account.attributes.country!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||||
|
<div class="">
|
||||||
|
<#if url.referrerURI??><a href="${url.referrerURI}">${msg("backToApplication")}/a></#if>
|
||||||
|
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
|
||||||
|
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</@layout.mainLayout>
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
parent=keycloak
|
|
@ -0,0 +1,72 @@
|
||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<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}}/users">Users</a></li>
|
||||||
|
<li>{{user.username}}</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<kc-tabs-user></kc-tabs-user>
|
||||||
|
|
||||||
|
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
|
||||||
|
<div class="form-group clearfix block">
|
||||||
|
<label class="col-md-2 control-label" for="street">Street</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="user.attributes.street" class="form-control" type="text" name="street" id="street" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>Street address.</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix block">
|
||||||
|
<label class="col-md-2 control-label" for="locality">City or Locality</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="user.attributes.locality" class="form-control" type="text" name="locality" id="locality" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>City or locality.</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix block">
|
||||||
|
<label class="col-md-2 control-label" for="region">State, Province, or Region</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="user.attributes.region" class="form-control" type="text" name="region" id="region" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>State, province, prefecture, or region.</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix block">
|
||||||
|
<label class="col-md-2 control-label" for="postal_code">Zip or Postal code</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="user.attributes.postal_code" class="form-control" type="text" name="postal_code" id="postal_code" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>Zip code or postal code.</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="form-group clearfix block">
|
||||||
|
<label class="col-md-2 control-label" for="country">Country</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input ng-model="user.attributes.country" class="form-control" type="text" name="country" id="country" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>Country name.</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="access.manageUsers">
|
||||||
|
<div class="col-md-10 col-md-offset-2">
|
||||||
|
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||||
|
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kc-menu></kc-menu>
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
parent=keycloak
|
|
@ -0,0 +1,95 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.registrationLayout; section>
|
||||||
|
<#if section = "title">
|
||||||
|
${msg("loginProfileTitle")}
|
||||||
|
<#elseif section = "header">
|
||||||
|
${msg("loginProfileTitle")}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="email" name="email" value="${(user.email!'')?html}" class="${properties.kcInputClass!}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="firstName" name="firstName" value="${(user.firstName!'')?html}" class="${properties.kcInputClass!}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="lastName" name="lastName" value="${(user.lastName!'')?html}" class="${properties.kcInputClass!}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street" value="${(user.attributes.street!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality" value="${(user.attributes.locality!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region" value="${(user.attributes.region!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(user.attributes.postal_code!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country" value="${(user.attributes.country!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||||
|
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||||
|
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -0,0 +1,131 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.registrationLayout; section>
|
||||||
|
<#if section = "title">
|
||||||
|
${msg("registerWithTitle",(realm.name!''))}
|
||||||
|
<#elseif section = "header">
|
||||||
|
${msg("registerWithTitleHtml",(realm.name!''))}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||||
|
<#if !realm.registrationEmailAsUsername>
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#if passwordRequired>
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street" value="${(register.formData['user.attributes.street']!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality" value="${(register.formData['user.attributes.locality']!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region" value="${(register.formData['user.attributes.region']!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(register.formData['user.attributes.postal_code']!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country" value="${(register.formData['user.attributes.country']!'')?html}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<#if recaptchaRequired??>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||||
|
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||||
|
<span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||||
|
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
parent=keycloak
|
|
@ -17,7 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.pages;
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
@ -96,6 +98,14 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
|
||||||
submitButton.click();
|
submitButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateAttribute(String attrName, String attrValue) {
|
||||||
|
WebElement attrElement = findAttributeInputElement(attrName);
|
||||||
|
attrElement.clear();
|
||||||
|
attrElement.sendKeys(attrValue);
|
||||||
|
submitButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void clickCancel() {
|
public void clickCancel() {
|
||||||
cancelButton.click();
|
cancelButton.click();
|
||||||
}
|
}
|
||||||
|
@ -117,6 +127,11 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
|
||||||
return emailInput.getAttribute("value");
|
return emailInput.getAttribute("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAttribute(String attrName) {
|
||||||
|
WebElement attrElement = findAttributeInputElement(attrName);
|
||||||
|
return attrElement.getAttribute("value");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCurrent() {
|
public boolean isCurrent() {
|
||||||
return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Edit Account");
|
return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Edit Account");
|
||||||
}
|
}
|
||||||
|
@ -140,4 +155,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
|
||||||
public boolean isPasswordUpdateSupported() {
|
public boolean isPasswordUpdateSupported() {
|
||||||
return driver.getPageSource().contains(getPath() + "/password");
|
return driver.getPageSource().contains(getPath() + "/password");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WebElement findAttributeInputElement(String attrName) {
|
||||||
|
String attrId = Constants.USER_ATTRIBUTES_PREFIX + attrName;
|
||||||
|
return driver.findElement(By.id(attrId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.account.custom;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.TestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.account.AccountTest;
|
||||||
|
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class CustomThemeTest extends TestRealmKeycloakTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
testRealm.setAccountTheme("address");
|
||||||
|
|
||||||
|
UserRepresentation user2 = UserBuilder.create()
|
||||||
|
.enabled(true)
|
||||||
|
.username("test-user-no-access@localhost")
|
||||||
|
.email("test-user-no-access@localhost")
|
||||||
|
.password("password")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RealmBuilder.edit(testRealm)
|
||||||
|
.user(user2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected AccountUpdateProfilePage profilePage;
|
||||||
|
|
||||||
|
// KEYCLOAK-3494
|
||||||
|
@Test
|
||||||
|
public void changeProfile() throws Exception {
|
||||||
|
profilePage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT).assertEvent();
|
||||||
|
|
||||||
|
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||||
|
Assert.assertEquals("", profilePage.getAttribute("street"));
|
||||||
|
|
||||||
|
profilePage.updateAttribute("street", "Elm 1");
|
||||||
|
Assert.assertEquals("Elm 1", profilePage.getAttribute("street"));
|
||||||
|
|
||||||
|
profilePage.updateAttribute("street", "Elm 2");
|
||||||
|
Assert.assertEquals("Elm 2", profilePage.getAttribute("street"));
|
||||||
|
|
||||||
|
events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -166,6 +166,14 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>federation-properties-example</artifactId>
|
<artifactId>federation-properties-example</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Dependency on services from integration-arquillian -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
|
<artifactId>integration-arquillian-testsuite-providers</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
|
|
@ -1,150 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite;
|
|
||||||
|
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
|
||||||
import org.keycloak.models.GroupModel;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
import org.keycloak.models.UserFederationProvider;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class DummyUserFederationProvider implements UserFederationProvider {
|
|
||||||
|
|
||||||
private final Map<String, UserModel> users;
|
|
||||||
|
|
||||||
public DummyUserFederationProvider(Map<String, UserModel> users) {
|
|
||||||
this.users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean synchronizeRegistrations() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserModel register(RealmModel realm, UserModel user) {
|
|
||||||
users.put(user.getUsername(), user);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
|
||||||
return users.remove(user.getUsername()) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserModel getUserByUsername(RealmModel realm, String username) {
|
|
||||||
return users.get(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserModel getUserByEmail(RealmModel realm, String email) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm, GroupModel group) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValid(RealmModel realm, UserModel local) {
|
|
||||||
String username = local.getUsername();
|
|
||||||
return users.containsKey(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getSupportedCredentialTypes(UserModel user) {
|
|
||||||
// Just user "test-user" is able to validate password with this federationProvider
|
|
||||||
if (user.getUsername().equals("test-user")) {
|
|
||||||
return Collections.singleton(UserCredentialModel.PASSWORD);
|
|
||||||
} else {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getSupportedCredentialTypes() {
|
|
||||||
return Collections.singleton(UserCredentialModel.PASSWORD);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
|
||||||
if (user.getUsername().equals("test-user") && input.size() == 1) {
|
|
||||||
UserCredentialModel password = input.get(0);
|
|
||||||
if (password.getType().equals(UserCredentialModel.PASSWORD)) {
|
|
||||||
return "secret".equals(password.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
|
|
||||||
return validCredentials(realm, user, Arrays.asList(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
|
|
||||||
return CredentialValidationOutput.failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.testsuite;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.models.UserFederationProvider;
|
|
||||||
import org.keycloak.models.UserFederationProviderFactory;
|
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.provider.ConfiguredProvider;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class DummyUserFederationProviderFactory implements UserFederationProviderFactory, ConfiguredProvider {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DummyUserFederationProviderFactory.class);
|
|
||||||
public static final String PROVIDER_NAME = "dummy";
|
|
||||||
|
|
||||||
private AtomicInteger fullSyncCounter = new AtomicInteger();
|
|
||||||
private AtomicInteger changedSyncCounter = new AtomicInteger();
|
|
||||||
|
|
||||||
private Map<String, UserModel> users = new HashMap<String, UserModel>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
|
|
||||||
return new DummyUserFederationProvider(users);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getConfigurationOptions() {
|
|
||||||
Set<String> list = new HashSet<String>();
|
|
||||||
list.add("important.config");
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserFederationProvider create(KeycloakSession session) {
|
|
||||||
return new DummyUserFederationProvider(users);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return PROVIDER_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
|
|
||||||
logger.info("syncAllUsers invoked");
|
|
||||||
fullSyncCounter.incrementAndGet();
|
|
||||||
return UserFederationSyncResult.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
|
|
||||||
logger.info("syncChangedUsers invoked");
|
|
||||||
changedSyncCounter.incrementAndGet();
|
|
||||||
return UserFederationSyncResult.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFullSyncCounter() {
|
|
||||||
return fullSyncCounter.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getChangedSyncCounter() {
|
|
||||||
return changedSyncCounter.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHelpText() {
|
|
||||||
return "Dummy User Federation Provider Help Text";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
|
||||||
|
|
||||||
ProviderConfigProperty prop1 = new ProviderConfigProperty();
|
|
||||||
prop1.setName("prop1");
|
|
||||||
prop1.setLabel("Prop1");
|
|
||||||
prop1.setDefaultValue("prop1Default");
|
|
||||||
prop1.setHelpText("Prop1 HelpText");
|
|
||||||
prop1.setType(ProviderConfigProperty.STRING_TYPE);
|
|
||||||
|
|
||||||
ProviderConfigProperty prop2 = new ProviderConfigProperty();
|
|
||||||
prop2.setName("prop2");
|
|
||||||
prop2.setLabel("Prop2");
|
|
||||||
prop2.setDefaultValue("true");
|
|
||||||
prop2.setHelpText("Prop2 HelpText");
|
|
||||||
prop2.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
|
||||||
|
|
||||||
return Arrays.asList(prop1, prop2);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -45,8 +45,8 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
|
||||||
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
|
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
|
||||||
|
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.NoSuchElementException;
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserFederationSyncResult;
|
import org.keycloak.models.UserFederationSyncResult;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.UsersSyncManager;
|
import org.keycloak.services.managers.UsersSyncManager;
|
||||||
import org.keycloak.testsuite.DummyUserFederationProviderFactory;
|
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.model;
|
package org.keycloak.testsuite.model;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@ -151,14 +152,17 @@ public class ConcurrentTransactionsTest extends AbstractModelTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// KEYCLOAK-3296
|
// KEYCLOAK-3296 , KEYCLOAK-3494
|
||||||
@Test
|
@Test
|
||||||
public void removeUserAttribute() throws Exception {
|
public void removeUserAttribute() throws Exception {
|
||||||
RealmModel realm = realmManager.createRealm("original");
|
RealmModel realm = realmManager.createRealm("original");
|
||||||
KeycloakSession session = realmManager.getSession();
|
KeycloakSession session = realmManager.getSession();
|
||||||
|
|
||||||
UserModel user = session.users().addUser(realm, "john");
|
UserModel john = session.users().addUser(realm, "john");
|
||||||
user.setSingleAttribute("foo", "val1");
|
john.setSingleAttribute("foo", "val1");
|
||||||
|
|
||||||
|
UserModel john2 = session.users().addUser(realm, "john2");
|
||||||
|
john2.setAttribute("foo", Arrays.asList("val1", "val2"));
|
||||||
|
|
||||||
final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||||
commit();
|
commit();
|
||||||
|
@ -182,12 +186,18 @@ public class ConcurrentTransactionsTest extends AbstractModelTest {
|
||||||
UserModel john = session.users().getUserByUsername("john", realm);
|
UserModel john = session.users().getUserByUsername("john", realm);
|
||||||
String attrVal = john.getFirstAttribute("foo");
|
String attrVal = john.getFirstAttribute("foo");
|
||||||
|
|
||||||
|
UserModel john2 = session.users().getUserByUsername("john2", realm);
|
||||||
|
String attrVal2 = john2.getFirstAttribute("foo");
|
||||||
|
|
||||||
// Wait until it's read in both threads
|
// Wait until it's read in both threads
|
||||||
readAttrLatch.countDown();
|
readAttrLatch.countDown();
|
||||||
readAttrLatch.await();
|
readAttrLatch.await();
|
||||||
|
|
||||||
// Remove user attribute in both threads
|
// KEYCLOAK-3296 : Remove user attribute in both threads
|
||||||
john.removeAttribute("foo");
|
john.removeAttribute("foo");
|
||||||
|
|
||||||
|
// KEYCLOAK-3494 : Set single attribute in both threads
|
||||||
|
john2.setSingleAttribute("foo", "bar");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,6 +211,31 @@ public class UserModelTest extends AbstractModelTest {
|
||||||
Assert.assertEquals("val23", attrVals.get(0));
|
Assert.assertEquals("val23", attrVals.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-3494
|
||||||
|
@Test
|
||||||
|
public void testUpdateUserAttribute() throws Exception {
|
||||||
|
RealmModel realm = realmManager.createRealm("original");
|
||||||
|
UserModel user = session.users().addUser(realm, "user");
|
||||||
|
|
||||||
|
user.setSingleAttribute("key1", "value1");
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
realm = realmManager.getRealmByName("original");
|
||||||
|
user = session.users().getUserByUsername("user", realm);
|
||||||
|
|
||||||
|
// Update attribute
|
||||||
|
List<String> attrVals = new ArrayList<>(Arrays.asList( "val2" ));
|
||||||
|
user.setAttribute("key1", attrVals);
|
||||||
|
Map<String, List<String>> allAttrVals = user.getAttributes();
|
||||||
|
|
||||||
|
// Ensure same transaction is able to see updated value
|
||||||
|
Assert.assertEquals(1, allAttrVals.size());
|
||||||
|
Assert.assertEquals(allAttrVals.get("key1"), Arrays.asList("val2"));
|
||||||
|
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchByString() {
|
public void testSearchByString() {
|
||||||
RealmModel realm = realmManager.createRealm("original");
|
RealmModel realm = realmManager.createRealm("original");
|
||||||
|
|
|
@ -15,5 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.testsuite.DummyUserFederationProviderFactory
|
|
||||||
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
|
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
|
Loading…
Reference in a new issue