parent
db9b6c2152
commit
d0691b0884
7 changed files with 169 additions and 12 deletions
|
@ -142,7 +142,16 @@ public interface Attributes {
|
|||
if (includeBuiltin) {
|
||||
return true;
|
||||
}
|
||||
return !isRootAttribute(entry.getKey());
|
||||
if (isRootAttribute(entry.getKey())) {
|
||||
if (UserModel.LOCALE.equals(entry.getKey()) && !entry.getValue().isEmpty()) {
|
||||
// locale is different form of built-in attribute in the sense it is related to a
|
||||
// specific feature (i18n) and does not have a top-level attribute in the user representation
|
||||
// the locale should be available from the attribute map if not empty
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
|
@ -163,7 +172,8 @@ public interface Attributes {
|
|||
return UserModel.USERNAME.equals(name)
|
||||
|| UserModel.EMAIL.equals(name)
|
||||
|| UserModel.FIRST_NAME.equals(name)
|
||||
|| UserModel.LAST_NAME.equals(name);
|
||||
|| UserModel.LAST_NAME.equals(name)
|
||||
|| UserModel.LOCALE.equals(name);
|
||||
}
|
||||
|
||||
Map<String, List<String>> toMap();
|
||||
|
|
|
@ -145,6 +145,11 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
return null;
|
||||
}
|
||||
|
||||
private static boolean isInternationalizationEnabled(AttributeContext context) {
|
||||
RealmModel realm = context.getSession().getContext().getRealm();
|
||||
return realm.isInternationalizationEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* There are the declarations for creating the built-in validations for read-only attributes. Regardless of the context where
|
||||
* user profiles are used. They are related to internal attributes with hard conditions on them in terms of management.
|
||||
|
@ -199,7 +204,7 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
}
|
||||
|
||||
addContextualProfileMetadata(configureUserProfile(createBrokeringProfile(readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(ACCOUNT, readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createAccountProfile(ACCOUNT, readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(ACCOUNT_OLD, readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(REGISTRATION_PROFILE, readOnlyValidator)));
|
||||
addContextualProfileMetadata(configureUserProfile(createDefaultProfile(UPDATE_PROFILE, readOnlyValidator)));
|
||||
|
@ -392,9 +397,11 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
}
|
||||
|
||||
readonlyValidators.add(createReadOnlyAttributeUnchangedValidator(adminReadOnlyAttributesPattern));
|
||||
|
||||
metadata.addAttribute(READ_ONLY_ATTRIBUTE_KEY, 1000, readonlyValidators);
|
||||
|
||||
metadata.addAttribute(UserModel.LOCALE, -1, AbstractUserProfileProvider::isInternationalizationEnabled, AbstractUserProfileProvider::isInternationalizationEnabled)
|
||||
.setRequired(AttributeMetadata.ALWAYS_FALSE);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
@ -415,4 +422,13 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
|||
|
||||
.build();
|
||||
}
|
||||
|
||||
private UserProfileMetadata createAccountProfile(UserProfileContext context, AttributeValidatorMetadata readOnlyValidator) {
|
||||
UserProfileMetadata defaultProfile = createDefaultProfile(context, readOnlyValidator);
|
||||
|
||||
defaultProfile.addAttribute(UserModel.LOCALE, -1, AbstractUserProfileProvider::isInternationalizationEnabled, AbstractUserProfileProvider::isInternationalizationEnabled)
|
||||
.setRequired(AttributeMetadata.ALWAYS_FALSE);
|
||||
|
||||
return defaultProfile;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class LegacyAttributes extends DefaultAttributes {
|
|||
|
||||
@Override
|
||||
protected boolean isIncludeAttributeIfNotProvided(AttributeMetadata metadata) {
|
||||
// user api expects that attributes are not updated if not provided when in legacy mode
|
||||
return UserProfileContext.USER_API.equals(context);
|
||||
// user api expects that built-in attributes are not updated if not provided when in legacy mode
|
||||
return UserProfileContext.USER_API.equals(context) && !isRootAttribute(metadata.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,9 @@ import org.keycloak.broker.provider.util.SimpleHttp;
|
|||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.account.UserProfileAttributeMetadata;
|
||||
import org.keycloak.representations.account.UserProfileMetadata;
|
||||
import org.keycloak.representations.account.UserRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
|
@ -367,6 +369,45 @@ public class AccountRestServiceWithUserProfileTest extends AccountRestServiceTes
|
|||
+ "]}");
|
||||
super.testUpdateSingleField();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManageUserLocaleAttribute() throws IOException {
|
||||
RealmRepresentation realmRep = testRealm().toRepresentation();
|
||||
Boolean internationalizationEnabled = realmRep.isInternationalizationEnabled();
|
||||
realmRep.setInternationalizationEnabled(false);
|
||||
testRealm().update(realmRep);
|
||||
UserRepresentation user = getUser();
|
||||
|
||||
try {
|
||||
user.getAttributes().put(UserModel.LOCALE, List.of("pt_BR"));
|
||||
user = updateAndGet(user);
|
||||
assertNull(user.getAttributes().get(UserModel.LOCALE));
|
||||
|
||||
realmRep.setInternationalizationEnabled(true);
|
||||
testRealm().update(realmRep);
|
||||
|
||||
user.getAttributes().put(UserModel.LOCALE, List.of("pt_BR"));
|
||||
user = updateAndGet(user);
|
||||
assertEquals("pt_BR", user.getAttributes().get(UserModel.LOCALE).get(0));
|
||||
|
||||
user.getAttributes().remove(UserModel.LOCALE);
|
||||
user = updateAndGet(user);
|
||||
assertNull(user.getAttributes().get(UserModel.LOCALE));
|
||||
|
||||
UserProfileMetadata metadata = user.getUserProfileMetadata();
|
||||
|
||||
assertTrue(metadata.getAttributes().stream()
|
||||
.map(UserProfileAttributeMetadata::getName)
|
||||
.filter(UserModel.LOCALE::equals).findAny()
|
||||
.isPresent()
|
||||
);
|
||||
} finally {
|
||||
realmRep.setInternationalizationEnabled(internationalizationEnabled);
|
||||
testRealm().update(realmRep);
|
||||
user.getAttributes().remove(UserModel.LOCALE);
|
||||
updateAndGet(user);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setUserProfileConfiguration(String configuration) {
|
||||
VerifyProfileTest.setUserProfileConfiguration(testRealm(), configuration);
|
||||
|
|
|
@ -8,6 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
|
||||
|
@ -21,6 +22,7 @@ import org.keycloak.admin.client.Keycloak;
|
|||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ErrorRepresentation;
|
||||
|
@ -340,6 +342,34 @@ public class DeclarativeUserTest extends AbstractAdminTest {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserLocale() {
|
||||
RealmRepresentation realmRep = realm.toRepresentation();
|
||||
Boolean internationalizationEnabled = realmRep.isInternationalizationEnabled();
|
||||
realmRep.setInternationalizationEnabled(true);
|
||||
realm.update(realmRep);
|
||||
|
||||
try {
|
||||
UserRepresentation user1 = new UserRepresentation();
|
||||
user1.setUsername("user1");
|
||||
user1.singleAttribute(UserModel.LOCALE, "pt_BR");
|
||||
String user1Id = createUser(user1);
|
||||
|
||||
UserResource userResource = realm.users().get(user1Id);
|
||||
user1 = userResource.toRepresentation();
|
||||
assertEquals("pt_BR", user1.getAttributes().get(UserModel.LOCALE).get(0));
|
||||
|
||||
realmRep.setInternationalizationEnabled(false);
|
||||
realm.update(realmRep);
|
||||
|
||||
user1 = userResource.toRepresentation();
|
||||
assertNull(user1.getAttributes().get(UserModel.LOCALE));
|
||||
} finally {
|
||||
realmRep.setInternationalizationEnabled(internationalizationEnabled);
|
||||
realm.update(realmRep);
|
||||
}
|
||||
}
|
||||
|
||||
private String createUser(UserRepresentation userRep) {
|
||||
Response response = realm.users().create(userRep);
|
||||
String createdId = ApiUtil.getCreatedId(response);
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.i18n;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.HttpClientBuilder;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.locale.LocaleSelectorProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LanguageComboboxAwarePage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.util.IdentityProviderBuilder;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
@EnableFeature(Feature.DECLARATIVE_USER_PROFILE)
|
||||
public class LoginPageWithUserProfileTest extends LoginPageTest {
|
||||
}
|
|
@ -332,7 +332,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
Attributes attributes = profile.getAttributes();
|
||||
|
||||
assertThat(attributes.nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, "address"));
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE, "address"));
|
||||
|
||||
try {
|
||||
profile.validate();
|
||||
|
@ -400,7 +400,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
Attributes attributes = profile.getAttributes();
|
||||
|
||||
assertThat(attributes.nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, "address", "second"));
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE, "address", "second"));
|
||||
|
||||
|
||||
AttributeGroupMetadata companyAddressGroup = attributes.getMetadata("address").getAttributeGroupMetadata();
|
||||
|
@ -517,7 +517,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
UserModel user = profile.create();
|
||||
|
||||
assertThat(profile.getAttributes().nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, "address", "department"));
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE, "address", "department"));
|
||||
|
||||
assertNull(user.getFirstAttribute("department"));
|
||||
|
||||
|
@ -567,7 +567,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
UserModel user = profile.create();
|
||||
|
||||
assertThat(profile.getAttributes().nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL));
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE));
|
||||
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||
|
||||
|
@ -609,7 +609,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
UserModel user = profile.create();
|
||||
|
||||
assertThat(profile.getAttributes().nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL));
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE));
|
||||
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||
|
||||
|
@ -649,7 +649,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
UserModel user = profile.create();
|
||||
|
||||
assertThat(profile.getAttributes().nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, "address", "department", "phone"));
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.LOCALE, "address", "department", "phone"));
|
||||
|
||||
profile = provider.create(UserProfileContext.USER_API, attributes, user);
|
||||
|
||||
|
|
Loading…
Reference in a new issue