Skip to content

Commit

Permalink
KEYCLOAK-17887 fix endpoint for creating or updating realm localizati…
Browse files Browse the repository at this point in the history
…on texts for a given locale (UnsupportedOperation was thrown because RealmAdapter tried to change unmodifiable map):

- fix RealmAdapter to create a new map instead of trying to change unmodifiable map
- only provide POST endpoints for creating or updating the texts (to have the endpoints consistent with other Admin API endpoints)
- add tests
  • Loading branch information
danielFesenmeyer authored and stianst committed Sep 30, 2021
1 parent d92bb7d commit 0a2f8f5
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
Expand Down Expand Up @@ -59,4 +60,9 @@ public interface RealmLocalizationResource {
@PUT
@Consumes(MediaType.TEXT_PLAIN)
void saveRealmLocalizationText(@PathParam("locale") String locale, @PathParam("key") String key, String text);

@Path("{locale}")
@POST
@Consumes("application/json")
void createOrUpdateRealmLocalizationTexts(@PathParam("locale") String locale, Map<String, String> localizationTexts);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1698,9 +1698,9 @@ public Map<String, String> getAttributes() {
}

@Override
public void patchRealmLocalizationTexts(String locale, Map<String, String> localizationTexts) {
public void createOrUpdateRealmLocalizationTexts(String locale, Map<String, String> localizationTexts) {
getDelegateForUpdate();
updated.patchRealmLocalizationTexts(locale, localizationTexts);
updated.createOrUpdateRealmLocalizationTexts(locale, localizationTexts);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2173,11 +2173,13 @@ private ComponentEntity getComponentEntity(String id) {
}

@Override
public void patchRealmLocalizationTexts(String locale, Map<String, String> localizationTexts) {
public void createOrUpdateRealmLocalizationTexts(String locale, Map<String, String> localizationTexts) {
Map<String, RealmLocalizationTextsEntity> currentLocalizationTexts = realm.getRealmLocalizationTexts();
if(currentLocalizationTexts.containsKey(locale)) {
RealmLocalizationTextsEntity localizationTextsEntity = currentLocalizationTexts.get(locale);
localizationTextsEntity.getTexts().putAll(localizationTexts);
Map<String, String> updatedTexts = new HashMap<>(localizationTextsEntity.getTexts());
updatedTexts.putAll(localizationTexts);
localizationTextsEntity.setTexts(updatedTexts);

em.persist(localizationTextsEntity);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1294,7 +1294,7 @@ public Stream<ClientScopeModel> getDefaultClientScopesStream(boolean defaultScop
}

@Override
public void patchRealmLocalizationTexts(String locale, Map<String, String> localizationTexts) {
public void createOrUpdateRealmLocalizationTexts(String locale, Map<String, String> localizationTexts) {
Map<String, Map<String, String>> realmLocalizationTexts = entity.getLocalizationTexts();

if (realmLocalizationTexts.containsKey(locale)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,15 @@ public void saveLocalizationText(RealmModel realm, String locale, String key, St
if (! updateLocalizationText(realm, locale, key, text)) {
Map<String, String> texts = new HashMap<>();
texts.put(key, text);
realm.patchRealmLocalizationTexts(locale, texts);
realm.createOrUpdateRealmLocalizationTexts(locale, texts);
}
}

//TODO move the following method to adapter
@Override
public void saveLocalizationTexts(RealmModel realm, String locale, Map<String, String> localizationTexts) {
if (locale == null || localizationTexts == null) return;
realm.patchRealmLocalizationTexts(locale, localizationTexts);
realm.createOrUpdateRealmLocalizationTexts(locale, localizationTexts);
}

//TODO move the following method to adapter
Expand All @@ -192,7 +192,7 @@ public boolean updateLocalizationText(RealmModel realm, String locale, String ke
if (locale == null || key == null || text == null || (! realm.getRealmLocalizationTextsByLocale(locale).containsKey(key))) return false;
Map<String, String> texts = new HashMap<>(realm.getRealmLocalizationTextsByLocale(locale));
texts.replace(key, text);
realm.patchRealmLocalizationTexts(locale, texts);
realm.createOrUpdateRealmLocalizationTexts(locale, texts);
return true;
}

Expand All @@ -210,7 +210,7 @@ public boolean deleteLocalizationText(RealmModel realm, String locale, String ke
Map<String, String> texts = new HashMap<>(realm.getRealmLocalizationTextsByLocale(locale));
texts.remove(key);
realm.removeRealmLocalizationTexts(locale);
realm.patchRealmLocalizationTexts(locale, texts);
realm.createOrUpdateRealmLocalizationTexts(locale, texts);
return true;
}

Expand Down
5 changes: 3 additions & 2 deletions server-spi/src/main/java/org/keycloak/models/RealmModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -992,10 +992,11 @@ default List<ClientScopeModel> getClientScopes() {
void removeDefaultClientScope(ClientScopeModel clientScope);

/**
* Patches the realm-specific localization texts. This method will not delete any text.
* Creates or updates the realm-specific localization texts for the given locale.
* This method will not delete any text.
* It updates texts, which are already stored or create new ones if the key does not exist yet.
*/
void patchRealmLocalizationTexts(String locale, Map<String, String> localizationTexts);
void createOrUpdateRealmLocalizationTexts(String locale, Map<String, String> localizationTexts);
boolean removeRealmLocalizationTexts(String locale);
Map<String, Map<String, String>> getRealmLocalizationTexts();
Map<String, String> getRealmLocalizationTextsByLocale(String locale);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public void saveRealmLocalizationText(@PathParam("locale") String locale, @PathP
@POST
@Path("{locale}")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public void patchRealmLocalizationTextsFromFile(@PathParam("locale") String locale, MultipartFormDataInput input)
throws IOException {
public void createOrUpdateRealmLocalizationTextsFromFile(@PathParam("locale") String locale,
MultipartFormDataInput input) {
this.auth.realm().requireManageRealm();

Map<String, List<InputPart>> formDataMap = input.getFormDataMap();
Expand All @@ -97,18 +97,19 @@ public void patchRealmLocalizationTextsFromFile(@PathParam("locale") String loca
TypeReference<HashMap<String, String>> typeRef = new TypeReference<HashMap<String, String>>() {
};
Map<String, String> rep = JsonSerialization.readValue(inputStream, typeRef);
realm.patchRealmLocalizationTexts(locale, rep);
realm.createOrUpdateRealmLocalizationTexts(locale, rep);
} catch (IOException e) {
throw new BadRequestException("Could not read file.");
}
}

@PATCH
@POST
@Path("{locale}")
@Consumes(MediaType.APPLICATION_JSON)
public void patchRealmLocalizationTexts(@PathParam("locale") String locale, Map<String, String> loclizationTexts) {
public void createOrUpdateRealmLocalizationTexts(@PathParam("locale") String locale,
Map<String, String> localizationTexts) {
this.auth.realm().requireManageRealm();
realm.patchRealmLocalizationTexts(locale, loclizationTexts);
realm.createOrUpdateRealmLocalizationTexts(locale, localizationTexts);
}

@Path("{locale}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@

package org.keycloak.testsuite.admin;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmLocalizationResource;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand All @@ -44,6 +45,7 @@ public void before() {

getCleanup().addLocalization("en");
getCleanup().addLocalization("de");
getCleanup().addLocalization("es");

resource = adminClient.realm(REALM_NAME).localization();
}
Expand Down Expand Up @@ -127,4 +129,31 @@ public void deleteRealmLocalizationTexts() {

assertThat(localizations, CoreMatchers.hasItems("de"));
}

@Test
public void createOrUpdateRealmLocalizationWhenLocaleDoesNotYetExist() {
final Map<String, String> newLocalizationTexts = new HashMap<>();
newLocalizationTexts.put("key-a", "text-a_es");
newLocalizationTexts.put("key-b", "text-b_es");

resource.createOrUpdateRealmLocalizationTexts("es", newLocalizationTexts);

final Map<String, String> persistedLocalizationTexts = resource.getRealmLocalizationTexts("es");
assertEquals(newLocalizationTexts, persistedLocalizationTexts);
}

@Test
public void createOrUpdateRealmLocalizationWhenLocaleAlreadyExists() {
final Map<String, String> newLocalizationTexts = new HashMap<>();
newLocalizationTexts.put("key-b", "text-b_changed_en");
newLocalizationTexts.put("key-c", "text-c_en");

resource.createOrUpdateRealmLocalizationTexts("en", newLocalizationTexts);

final Map<String, String> expectedLocalizationTexts = new HashMap<>();
expectedLocalizationTexts.put("key-a", "text-a_en");
expectedLocalizationTexts.putAll(newLocalizationTexts);
final Map<String, String> persistedLocalizationTexts = resource.getRealmLocalizationTexts("en");
assertEquals(expectedLocalizationTexts, persistedLocalizationTexts);
}
}

0 comments on commit 0a2f8f5

Please sign in to comment.