View Javadoc
1   /*
2    * Copyright (C) 2023 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   package org.tailormap.api.persistence;
7   
8   import jakarta.persistence.Column;
9   import jakarta.persistence.Entity;
10  import jakarta.persistence.EntityListeners;
11  import jakarta.persistence.Id;
12  import jakarta.persistence.ManyToMany;
13  import jakarta.persistence.PreRemove;
14  import jakarta.persistence.Table;
15  import jakarta.persistence.Version;
16  import jakarta.validation.constraints.Pattern;
17  import java.time.OffsetDateTime;
18  import java.time.ZoneId;
19  import java.util.ArrayList;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Set;
23  import java.util.function.Function;
24  import org.hibernate.annotations.JdbcTypeCode;
25  import org.hibernate.envers.Audited;
26  import org.hibernate.envers.NotAudited;
27  import org.hibernate.type.SqlTypes;
28  import org.springframework.data.jpa.domain.support.AuditingEntityListener;
29  import org.tailormap.api.persistence.helper.AdminAdditionalPropertyHelper;
30  import org.tailormap.api.persistence.json.AdminAdditionalProperty;
31  import org.tailormap.api.persistence.json.GroupOidcInfo;
32  import org.tailormap.api.persistence.listener.EntityEventPublisher;
33  import org.tailormap.api.util.Constants;
34  
35  @Audited
36  @Entity
37  @Table(name = "groups")
38  @EntityListeners({EntityEventPublisher.class, AuditingEntityListener.class})
39  public class Group extends AuditMetadata {
40    // Group to make authorization rules for anonymous users
41    public static final String ANONYMOUS = "anonymous";
42    // Group to make authorization rules for authenticated users
43    public static final String AUTHENTICATED = "authenticated";
44    public static final String ADMIN = "admin";
45    public static final String ACTUATOR = "actuator";
46  
47    @Id
48    @Pattern(regexp = Constants.NAME_REGEX, message = "Group " + Constants.NAME_REGEX_INVALID_MESSAGE) private String name;
49  
50    @Version
51    private Long version;
52  
53    private boolean systemGroup;
54  
55    private String description;
56  
57    @Column(columnDefinition = "text")
58    private String notes;
59  
60    @ManyToMany(mappedBy = "groups")
61    private Set<User> members = new HashSet<>();
62  
63    /**
64     * Enables the use of a group as an alias for another group. This is useful for example when the 'admin' group name
65     * can't be sent from a single sign-on provider. In that case, the single sign-on provider can send a different
66     * group name and the viewer can map that group name to the 'admin' group.
67     */
68    private String aliasForGroup;
69  
70    /**
71     * Generic additional properties which can be set on a group. A viewer admin frontend extension component can define
72     * attributes for the purposes of the extension and the viewer admin UI will show a control to edit the attribute in
73     * the group detail form.
74     */
75    @JdbcTypeCode(SqlTypes.JSON)
76    @Column(columnDefinition = "jsonb")
77    private List<AdminAdditionalProperty> additionalProperties = new ArrayList<>();
78  
79    @NotAudited
80    @JdbcTypeCode(SqlTypes.JSON)
81    @Column(columnDefinition = "jsonb")
82    private GroupOidcInfo oidcInfo;
83  
84    public String getName() {
85      return name;
86    }
87  
88    public Group setName(String name) {
89      this.name = name;
90      return this;
91    }
92  
93    public Long getVersion() {
94      return version;
95    }
96  
97    public Group setVersion(Long version) {
98      this.version = version;
99      return this;
100   }
101 
102   public boolean isSystemGroup() {
103     return systemGroup;
104   }
105 
106   public Group setSystemGroup(boolean systemGroup) {
107     this.systemGroup = systemGroup;
108     return this;
109   }
110 
111   public String getDescription() {
112     return description;
113   }
114 
115   public Group setDescription(String title) {
116     this.description = title;
117     return this;
118   }
119 
120   public String getNotes() {
121     return notes;
122   }
123 
124   public Group setNotes(String notes) {
125     this.notes = notes;
126     return this;
127   }
128 
129   public Set<User> getMembers() {
130     return members;
131   }
132 
133   public Group setMembers(Set<User> members) {
134     this.members = members;
135     return this;
136   }
137 
138   public String getAliasForGroup() {
139     return aliasForGroup;
140   }
141 
142   public Group setAliasForGroup(String aliasFor) {
143     this.aliasForGroup = aliasFor;
144     return this;
145   }
146 
147   public List<AdminAdditionalProperty> getAdditionalProperties() {
148     return additionalProperties;
149   }
150 
151   public Group setAdditionalProperties(List<AdminAdditionalProperty> additionalProperties) {
152     this.additionalProperties = additionalProperties;
153     return this;
154   }
155 
156   public GroupOidcInfo getOidcInfo() {
157     return oidcInfo;
158   }
159 
160   public Group setOidcInfo(GroupOidcInfo oidcInfo) {
161     this.oidcInfo = oidcInfo;
162     return this;
163   }
164 
165   @PreRemove
166   @SuppressWarnings("PMD.UnusedPrivateMethod")
167   private void removeMembers() {
168     for (User user : this.members) {
169       user.getGroups().remove(this);
170     }
171   }
172 
173   public void addOrUpdateAdminProperty(String key, Object value, boolean isPublic) {
174     AdminAdditionalPropertyHelper.addOrUpdateAdminProperty(additionalProperties, key, value, isPublic);
175   }
176 
177   /**
178    * Maps a property value in the additional properties of the group. If the property does not exist, it will be
179    * created, and the valueMapper function will be called with a null value.
180    *
181    * @param key the key of the property
182    * @param isPublic whether the property is public
183    * @param valueMapper the function to map the value
184    */
185   public void mapAdminPropertyValue(String key, boolean isPublic, Function<Object, Object> valueMapper) {
186     AdminAdditionalPropertyHelper.mapAdminPropertyValue(additionalProperties, key, isPublic, valueMapper);
187   }
188 
189   public void oidcClientIdSeen(String clientId) {
190     if (oidcInfo == null) {
191       oidcInfo = new GroupOidcInfo();
192     }
193     oidcInfo.addClientIdsItem(clientId);
194     oidcInfo.putLastSeenByClientIdItem(clientId, OffsetDateTime.now(ZoneId.of("UTC")));
195   }
196 }