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 REFRESH_CAPABILITIES = "refresh-capabilities";
46    public static final String ACTUATOR = "actuator";
47  
48    @Id
49    @Pattern(regexp = Constants.NAME_REGEX, message = "Group " + Constants.NAME_REGEX_INVALID_MESSAGE) private String name;
50  
51    @Version
52    private Long version;
53  
54    private boolean systemGroup;
55  
56    private String description;
57  
58    @Column(columnDefinition = "text")
59    private String notes;
60  
61    @ManyToMany(mappedBy = "groups")
62    private Set<User> members = new HashSet<>();
63  
64    /**
65     * Enables the use of a group as an alias for another group. This is useful for example when the 'admin' group name
66     * can't be sent from a single sign-on provider. In that case, the single sign-on provider can send a different
67     * group name and the viewer can map that group name to the 'admin' group.
68     */
69    private String aliasForGroup;
70  
71    /**
72     * Generic additional properties which can be set on a group. A viewer admin frontend extension component can define
73     * attributes for the purposes of the extension and the viewer admin UI will show a control to edit the attribute in
74     * the group detail form.
75     */
76    @JdbcTypeCode(SqlTypes.JSON)
77    @Column(columnDefinition = "jsonb")
78    private List<AdminAdditionalProperty> additionalProperties = new ArrayList<>();
79  
80    @NotAudited
81    @JdbcTypeCode(SqlTypes.JSON)
82    @Column(columnDefinition = "jsonb")
83    private GroupOidcInfo oidcInfo;
84  
85    public String getName() {
86      return name;
87    }
88  
89    public Group setName(String name) {
90      this.name = name;
91      return this;
92    }
93  
94    public Long getVersion() {
95      return version;
96    }
97  
98    public Group setVersion(Long version) {
99      this.version = version;
100     return this;
101   }
102 
103   public boolean isSystemGroup() {
104     return systemGroup;
105   }
106 
107   public Group setSystemGroup(boolean systemGroup) {
108     this.systemGroup = systemGroup;
109     return this;
110   }
111 
112   public String getDescription() {
113     return description;
114   }
115 
116   public Group setDescription(String title) {
117     this.description = title;
118     return this;
119   }
120 
121   public String getNotes() {
122     return notes;
123   }
124 
125   public Group setNotes(String notes) {
126     this.notes = notes;
127     return this;
128   }
129 
130   public Set<User> getMembers() {
131     return members;
132   }
133 
134   public Group setMembers(Set<User> members) {
135     this.members = members;
136     return this;
137   }
138 
139   public String getAliasForGroup() {
140     return aliasForGroup;
141   }
142 
143   public Group setAliasForGroup(String aliasFor) {
144     this.aliasForGroup = aliasFor;
145     return this;
146   }
147 
148   public List<AdminAdditionalProperty> getAdditionalProperties() {
149     return additionalProperties;
150   }
151 
152   public Group setAdditionalProperties(List<AdminAdditionalProperty> additionalProperties) {
153     this.additionalProperties = additionalProperties;
154     return this;
155   }
156 
157   public GroupOidcInfo getOidcInfo() {
158     return oidcInfo;
159   }
160 
161   public Group setOidcInfo(GroupOidcInfo oidcInfo) {
162     this.oidcInfo = oidcInfo;
163     return this;
164   }
165 
166   @PreRemove
167   @SuppressWarnings("PMD.UnusedPrivateMethod")
168   private void removeMembers() {
169     for (User user : this.members) {
170       user.getGroups().remove(this);
171     }
172   }
173 
174   public void addOrUpdateAdminProperty(String key, Object value, boolean isPublic) {
175     AdminAdditionalPropertyHelper.addOrUpdateAdminProperty(additionalProperties, key, value, isPublic);
176   }
177 
178   /**
179    * Maps a property value in the additional properties of the group. If the property does not exist, it will be
180    * created, and the valueMapper function will be called with a null value.
181    *
182    * @param key the key of the property
183    * @param isPublic whether the property is public
184    * @param valueMapper the function to map the value
185    */
186   public void mapAdminPropertyValue(String key, boolean isPublic, Function<Object, Object> valueMapper) {
187     AdminAdditionalPropertyHelper.mapAdminPropertyValue(additionalProperties, key, isPublic, valueMapper);
188   }
189 
190   public void oidcClientIdSeen(String clientId) {
191     if (oidcInfo == null) {
192       oidcInfo = new GroupOidcInfo();
193     }
194     oidcInfo.addClientIdsItem(clientId);
195     oidcInfo.putLastSeenByClientIdItem(clientId, OffsetDateTime.now(ZoneId.of("UTC")));
196   }
197 }