Group.java
/*
* Copyright (C) 2023 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.persistence;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.PreRemove;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import jakarta.validation.constraints.Pattern;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;
import org.hibernate.type.SqlTypes;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.tailormap.api.persistence.helper.AdminAdditionalPropertyHelper;
import org.tailormap.api.persistence.json.AdminAdditionalProperty;
import org.tailormap.api.persistence.json.GroupOidcInfo;
import org.tailormap.api.persistence.listener.EntityEventPublisher;
import org.tailormap.api.util.Constants;
@Audited
@Entity
@Table(name = "groups")
@EntityListeners({EntityEventPublisher.class, AuditingEntityListener.class})
public class Group extends AuditMetadata {
// Group to make authorization rules for anonymous users
public static final String ANONYMOUS = "anonymous";
// Group to make authorization rules for authenticated users
public static final String AUTHENTICATED = "authenticated";
public static final String ADMIN = "admin";
public static final String ACTUATOR = "actuator";
@Id
@Pattern(regexp = Constants.NAME_REGEX, message = "Group " + Constants.NAME_REGEX_INVALID_MESSAGE) private String name;
@Version
private Long version;
private boolean systemGroup;
private String description;
@Column(columnDefinition = "text")
private String notes;
@ManyToMany(mappedBy = "groups")
private Set<User> members = new HashSet<>();
/**
* Enables the use of a group as an alias for another group. This is useful for example when the 'admin' group name
* can't be sent from a single sign-on provider. In that case, the single sign-on provider can send a different
* group name and the viewer can map that group name to the 'admin' group.
*/
private String aliasForGroup;
/**
* Generic additional properties which can be set on a group. A viewer admin frontend extension component can define
* attributes for the purposes of the extension and the viewer admin UI will show a control to edit the attribute in
* the group detail form.
*/
@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "jsonb")
private List<AdminAdditionalProperty> additionalProperties = new ArrayList<>();
@NotAudited
@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "jsonb")
private GroupOidcInfo oidcInfo;
public String getName() {
return name;
}
public Group setName(String name) {
this.name = name;
return this;
}
public Long getVersion() {
return version;
}
public Group setVersion(Long version) {
this.version = version;
return this;
}
public boolean isSystemGroup() {
return systemGroup;
}
public Group setSystemGroup(boolean systemGroup) {
this.systemGroup = systemGroup;
return this;
}
public String getDescription() {
return description;
}
public Group setDescription(String title) {
this.description = title;
return this;
}
public String getNotes() {
return notes;
}
public Group setNotes(String notes) {
this.notes = notes;
return this;
}
public Set<User> getMembers() {
return members;
}
public Group setMembers(Set<User> members) {
this.members = members;
return this;
}
public String getAliasForGroup() {
return aliasForGroup;
}
public Group setAliasForGroup(String aliasFor) {
this.aliasForGroup = aliasFor;
return this;
}
public List<AdminAdditionalProperty> getAdditionalProperties() {
return additionalProperties;
}
public Group setAdditionalProperties(List<AdminAdditionalProperty> additionalProperties) {
this.additionalProperties = additionalProperties;
return this;
}
public GroupOidcInfo getOidcInfo() {
return oidcInfo;
}
public Group setOidcInfo(GroupOidcInfo oidcInfo) {
this.oidcInfo = oidcInfo;
return this;
}
@PreRemove
@SuppressWarnings("PMD.UnusedPrivateMethod")
private void removeMembers() {
for (User user : this.members) {
user.getGroups().remove(this);
}
}
public void addOrUpdateAdminProperty(String key, Object value, boolean isPublic) {
AdminAdditionalPropertyHelper.addOrUpdateAdminProperty(additionalProperties, key, value, isPublic);
}
/**
* Maps a property value in the additional properties of the group. If the property does not exist, it will be
* created, and the valueMapper function will be called with a null value.
*
* @param key the key of the property
* @param isPublic whether the property is public
* @param valueMapper the function to map the value
*/
public void mapAdminPropertyValue(String key, boolean isPublic, Function<Object, Object> valueMapper) {
AdminAdditionalPropertyHelper.mapAdminPropertyValue(additionalProperties, key, isPublic, valueMapper);
}
public void oidcClientIdSeen(String clientId) {
if (oidcInfo == null) {
oidcInfo = new GroupOidcInfo();
}
oidcInfo.addClientIdsItem(clientId);
oidcInfo.putLastSeenByClientIdItem(clientId, OffsetDateTime.now(ZoneId.of("UTC")));
}
}