Manage organization attributes

Closes #28253

Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
Martin Kanis 2024-04-09 14:45:11 +02:00 committed by Pedro Igor
parent 41b706bb6a
commit 51fa054ba7
5 changed files with 131 additions and 0 deletions

View file

@ -17,10 +17,16 @@
package org.keycloak.representations.idm; package org.keycloak.representations.idm;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OrganizationRepresentation { public class OrganizationRepresentation {
private String id; private String id;
private String name; private String name;
private Map<String, List<String>> attributes = new HashMap<>();
public String getId() { public String getId() {
return id; return id;
@ -38,6 +44,20 @@ public class OrganizationRepresentation {
return name; return name;
} }
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.attributes = attributes;
}
public OrganizationRepresentation singleAttribute(String name, String value) {
if (this.attributes == null) attributes = new HashMap<>();
attributes.put(name, Arrays.asList(value));
return this;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -17,15 +17,21 @@
package org.keycloak.organization.jpa; package org.keycloak.organization.jpa;
import org.keycloak.models.GroupModel;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.jpa.JpaModel; import org.keycloak.models.jpa.JpaModel;
import org.keycloak.models.jpa.entities.OrganizationEntity; import org.keycloak.models.jpa.entities.OrganizationEntity;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public final class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> { public final class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> {
private final RealmModel realm; private final RealmModel realm;
private final OrganizationEntity entity; private final OrganizationEntity entity;
private GroupModel group;
public OrganizationAdapter(RealmModel realm, OrganizationEntity entity) { public OrganizationAdapter(RealmModel realm, OrganizationEntity entity) {
this.realm = realm; this.realm = realm;
@ -55,11 +61,48 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
return entity.getName(); return entity.getName();
} }
@Override
public void setSingleAttribute(String name, String value) {
getGroup().setSingleAttribute(name, value);
}
@Override
public void setAttribute(String name, List<String> values) {
getGroup().setAttribute(name, values);
}
@Override
public void removeAttribute(String name) {
getGroup().removeAttribute(name);
}
@Override
public String getFirstAttribute(String name) {
return getGroup().getFirstAttribute(name);
}
@Override
public Stream<String> getAttributeStream(String name) {
return getGroup().getAttributeStream(name);
}
@Override
public Map<String, List<String>> getAttributes() {
return getGroup().getAttributes();
}
@Override @Override
public OrganizationEntity getEntity() { public OrganizationEntity getEntity() {
return entity; return entity;
} }
private GroupModel getGroup() {
if (group == null) {
group = realm.getGroupById(getGroupId());
}
return group;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder() return new StringBuilder()

View file

@ -17,6 +17,10 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public interface OrganizationModel { public interface OrganizationModel {
String USER_ORGANIZATION_ATTRIBUTE = "kc.org"; String USER_ORGANIZATION_ATTRIBUTE = "kc.org";
@ -26,4 +30,16 @@ public interface OrganizationModel {
void setName(String name); void setName(String name);
String getName(); String getName();
void setSingleAttribute(String name, String value);
void setAttribute(String name, List<String> values);
void removeAttribute(String name);
String getFirstAttribute(String name);
Stream<String> getAttributeStream(String name);
Map<String, List<String>> getAttributes();
} }

View file

@ -17,6 +17,7 @@
package org.keycloak.organization.admin.resource; package org.keycloak.organization.admin.resource;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.BadRequestException;
@ -141,6 +142,7 @@ public class OrganizationResource {
rep.setId(model.getId()); rep.setId(model.getId());
rep.setName(model.getName()); rep.setName(model.getName());
rep.setAttributes(model.getAttributes());
return rep; return rep;
} }
@ -152,6 +154,14 @@ public class OrganizationResource {
model.setName(rep.getName()); model.setName(rep.getName());
if (rep.getAttributes() != null) {
Set<String> attrsToRemove = model.getAttributes().keySet();
attrsToRemove.removeAll(rep.getAttributes().keySet());
attrsToRemove.forEach(model::removeAttribute);
rep.getAttributes().entrySet().forEach(entry -> model.setAttribute(entry.getKey(), entry.getValue()));
}
return model; return model;
} }
} }

View file

@ -20,9 +20,11 @@ package org.keycloak.testsuite.organization.admin;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotFoundException;
@ -93,4 +95,44 @@ public class OrganizationTest extends AbstractOrganizationTest {
fail("should be deleted"); fail("should be deleted");
} catch (NotFoundException ignore) {} } catch (NotFoundException ignore) {}
} }
@Test
public void testAttributes() {
OrganizationRepresentation org = createOrganization();
org = org.singleAttribute("key", "value");
OrganizationResource organization = testRealm().organizations().get(org.getId());
try (Response response = organization.update(org)) {
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
OrganizationRepresentation updated = organization.toRepresentation();
assertEquals(org.getAttributes().get("key"), updated.getAttributes().get("key"));
HashMap<String, List<String>> attributes = new HashMap<>();
attributes.put("attr1", List.of("val11", "val12"));
attributes.put("attr2", List.of("val21", "val22"));
org.setAttributes(attributes);
try (Response response = organization.update(org)) {
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
updated = organization.toRepresentation();
assertNull(updated.getAttributes().get("key"));
assertEquals(2, updated.getAttributes().size());
assertEquals(org.getAttributes().get("attr1"), updated.getAttributes().get("attr1"));
assertEquals(org.getAttributes().get("attr2"), updated.getAttributes().get("attr2"));
attributes.clear();
org.setAttributes(attributes);
try (Response response = organization.update(org)) {
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
updated = organization.toRepresentation();
assertEquals(0, updated.getAttributes().size());
}
} }