View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.persistence.jpa.entity.user;
20  
21  import com.fasterxml.jackson.core.type.TypeReference;
22  import java.time.OffsetDateTime;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Optional;
26  import java.util.stream.Collectors;
27  import javax.persistence.Cacheable;
28  import javax.persistence.CascadeType;
29  import javax.persistence.Column;
30  import javax.persistence.Entity;
31  import javax.persistence.EnumType;
32  import javax.persistence.Enumerated;
33  import javax.persistence.FetchType;
34  import javax.persistence.JoinColumn;
35  import javax.persistence.JoinTable;
36  import javax.persistence.Lob;
37  import javax.persistence.ManyToMany;
38  import javax.persistence.ManyToOne;
39  import javax.persistence.OneToMany;
40  import javax.persistence.Table;
41  import javax.persistence.UniqueConstraint;
42  import javax.validation.Valid;
43  import javax.validation.constraints.NotNull;
44  import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
45  import org.apache.syncope.common.lib.types.CipherAlgorithm;
46  import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
47  import org.apache.syncope.core.persistence.api.entity.AnyType;
48  import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
49  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
50  import org.apache.syncope.core.persistence.api.entity.RelationshipType;
51  import org.apache.syncope.core.persistence.api.entity.Role;
52  import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
53  import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
54  import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
55  import org.apache.syncope.core.persistence.api.entity.user.UMembership;
56  import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
57  import org.apache.syncope.core.persistence.api.entity.user.URelationship;
58  import org.apache.syncope.core.persistence.api.entity.user.User;
59  import org.apache.syncope.core.persistence.jpa.entity.AbstractGroupableRelatable;
60  import org.apache.syncope.core.persistence.jpa.entity.JPAAnyTypeClass;
61  import org.apache.syncope.core.persistence.jpa.entity.JPAExternalResource;
62  import org.apache.syncope.core.persistence.jpa.entity.JPARole;
63  import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
64  import org.apache.syncope.core.spring.ApplicationContextProvider;
65  import org.apache.syncope.core.spring.security.AuthContextUtils;
66  import org.apache.syncope.core.spring.security.Encryptor;
67  import org.apache.syncope.core.spring.security.SecureRandomUtils;
68  
69  @Entity
70  @Table(name = JPAUser.TABLE)
71  @Cacheable
72  public class JPAUser
73          extends AbstractGroupableRelatable<User, UMembership, UPlainAttr, AnyObject, URelationship>
74          implements User {
75  
76      private static final long serialVersionUID = -3905046855521446823L;
77  
78      public static final String TABLE = "SyncopeUser";
79  
80      protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
81  
82      protected static final TypeReference<List<String>> TYPEREF = new TypeReference<List<String>>() {
83      };
84  
85      @Column(nullable = true)
86      protected String password;
87  
88      @ManyToMany(fetch = FetchType.EAGER)
89      @JoinTable(joinColumns =
90              @JoinColumn(name = "user_id"),
91              inverseJoinColumns =
92              @JoinColumn(name = "role_id"),
93              uniqueConstraints =
94              @UniqueConstraint(columnNames = { "user_id", "role_id" }))
95      protected List<JPARole> roles = new ArrayList<>();
96  
97      @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
98      @Valid
99      protected List<JPAUPlainAttr> plainAttrs = new ArrayList<>();
100 
101     @Column(nullable = true)
102     protected String status;
103 
104     @Lob
105     protected String token;
106 
107     protected OffsetDateTime tokenExpireTime;
108 
109     @Column(nullable = true)
110     @Enumerated(EnumType.STRING)
111     protected CipherAlgorithm cipherAlgorithm;
112 
113     @Lob
114     protected String passwordHistory;
115 
116     /**
117      * Subsequent failed logins.
118      */
119     @Column(nullable = true)
120     protected Integer failedLogins;
121 
122     /**
123      * Username/Login.
124      */
125     @Column(unique = true)
126     @NotNull(message = "Blank username")
127     protected String username;
128 
129     /**
130      * Last successful login date.
131      */
132     protected OffsetDateTime lastLoginDate;
133 
134     /**
135      * Change password date.
136      */
137     protected OffsetDateTime changePwdDate;
138 
139     protected Boolean suspended = false;
140 
141     protected Boolean mustChangePassword = false;
142 
143     /**
144      * Provisioning external resources.
145      */
146     @ManyToMany(fetch = FetchType.EAGER)
147     @JoinTable(joinColumns =
148             @JoinColumn(name = "user_id"),
149             inverseJoinColumns =
150             @JoinColumn(name = "resource_id"),
151             uniqueConstraints =
152             @UniqueConstraint(columnNames = { "user_id", "resource_id" }))
153     protected List<JPAExternalResource> resources = new ArrayList<>();
154 
155     @ManyToMany(fetch = FetchType.LAZY)
156     @JoinTable(joinColumns =
157             @JoinColumn(name = "user_id"),
158             inverseJoinColumns =
159             @JoinColumn(name = "anyTypeClass_id"),
160             uniqueConstraints =
161             @UniqueConstraint(columnNames = { "user_id", "anyTypeClass_id" }))
162     protected List<JPAAnyTypeClass> auxClasses = new ArrayList<>();
163 
164     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "leftEnd")
165     @Valid
166     protected List<JPAURelationship> relationships = new ArrayList<>();
167 
168     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "leftEnd")
169     @Valid
170     protected List<JPAUMembership> memberships = new ArrayList<>();
171 
172     @ManyToOne(fetch = FetchType.EAGER)
173     protected JPASecurityQuestion securityQuestion;
174 
175     @Column(nullable = true)
176     protected String securityAnswer;
177 
178     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "owner")
179     @Valid
180     protected List<JPALinkedAccount> linkedAccounts = new ArrayList<>();
181 
182     @Override
183     public AnyType getType() {
184         return ApplicationContextProvider.getBeanFactory().getBean(AnyTypeDAO.class).findUser();
185     }
186 
187     @Override
188     public void setType(final AnyType type) {
189         // nothing to do
190     }
191 
192     @Override
193     public boolean add(final ExternalResource resource) {
194         checkType(resource, JPAExternalResource.class);
195         return resources.contains((JPAExternalResource) resource) || resources.add((JPAExternalResource) resource);
196     }
197 
198     @Override
199     public List<? extends ExternalResource> getResources() {
200         return resources;
201     }
202 
203     @Override
204     public boolean add(final Role role) {
205         checkType(role, JPARole.class);
206         return roles.contains((JPARole) role) || roles.add((JPARole) role);
207     }
208 
209     @Override
210     public List<? extends Role> getRoles() {
211         return roles;
212     }
213 
214     @Override
215     public String getPassword() {
216         return password;
217     }
218 
219     @Override
220     public void setEncodedPassword(final String password, final CipherAlgorithm cipherAlgorithm) {
221         this.password = password;
222         this.cipherAlgorithm = cipherAlgorithm;
223         setMustChangePassword(false);
224     }
225 
226     @Override
227     public void setPassword(final String password) {
228         try {
229             this.password = ENCRYPTOR.encode(password, cipherAlgorithm == null
230                     ? CipherAlgorithm.valueOf(ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).
231                             get(AuthContextUtils.getDomain(), "password.cipher.algorithm", CipherAlgorithm.AES.name(),
232                                     String.class))
233                     : cipherAlgorithm);
234             setMustChangePassword(false);
235         } catch (Exception e) {
236             LOG.error("Could not encode password", e);
237             this.password = null;
238         }
239     }
240 
241     @Override
242     public CipherAlgorithm getCipherAlgorithm() {
243         return cipherAlgorithm;
244     }
245 
246     @Override
247     public void setCipherAlgorithm(final CipherAlgorithm cipherAlgorithm) {
248         if (this.cipherAlgorithm == null || cipherAlgorithm == null) {
249             this.cipherAlgorithm = cipherAlgorithm;
250         } else {
251             throw new IllegalArgumentException("Cannot override existing cipher algorithm");
252         }
253     }
254 
255     @Override
256     public boolean canDecodeSecrets() {
257         return this.cipherAlgorithm != null && this.cipherAlgorithm.isInvertible();
258     }
259 
260     @Override
261     public boolean add(final UPlainAttr attr) {
262         checkType(attr, JPAUPlainAttr.class);
263         return plainAttrs.add((JPAUPlainAttr) attr);
264     }
265 
266     @Override
267     protected List<? extends UPlainAttr> internalGetPlainAttrs() {
268         return plainAttrs;
269     }
270 
271     @Override
272     public String getStatus() {
273         return status;
274     }
275 
276     @Override
277     public void setStatus(final String status) {
278         this.status = status;
279     }
280 
281     @Override
282     public void generateToken(final int tokenLength, final int tokenExpireTime) {
283         this.token = SecureRandomUtils.generateRandomPassword(tokenLength);
284         this.tokenExpireTime = OffsetDateTime.now().plusMinutes(tokenExpireTime);
285     }
286 
287     @Override
288     public void removeToken() {
289         this.token = null;
290         this.tokenExpireTime = null;
291     }
292 
293     @Override
294     public String getToken() {
295         return token;
296     }
297 
298     @Override
299     public OffsetDateTime getTokenExpireTime() {
300         return tokenExpireTime;
301     }
302 
303     @Override
304     public boolean checkToken(final String token) {
305         return Optional.ofNullable(this.token).
306                 map(s -> s.equals(token) && !hasTokenExpired()).
307                 orElseGet(() -> token == null);
308     }
309 
310     @Override
311     public boolean hasTokenExpired() {
312         return Optional.ofNullable(tokenExpireTime).
313                 filter(expireTime -> expireTime.isBefore(OffsetDateTime.now())).
314                 isPresent();
315     }
316 
317     @Override
318     public void addToPasswordHistory(final String password) {
319         List<String> ph = getPasswordHistory();
320         ph.add(password);
321         passwordHistory = POJOHelper.serialize(ph);
322     }
323 
324     @Override
325     public void removeOldestEntriesFromPasswordHistory(final int n) {
326         List<String> ph = getPasswordHistory();
327         ph.subList(n, ph.size());
328         passwordHistory = POJOHelper.serialize(ph);
329     }
330 
331     @Override
332     public List<String> getPasswordHistory() {
333         return passwordHistory == null
334                 ? new ArrayList<>(0)
335                 : POJOHelper.deserialize(passwordHistory, TYPEREF);
336     }
337 
338     @Override
339     public OffsetDateTime getChangePwdDate() {
340         return changePwdDate;
341     }
342 
343     @Override
344     public void setChangePwdDate(final OffsetDateTime changePwdDate) {
345         this.changePwdDate = changePwdDate;
346     }
347 
348     @Override
349     public Integer getFailedLogins() {
350         return failedLogins == null ? 0 : failedLogins;
351     }
352 
353     @Override
354     public void setFailedLogins(final Integer failedLogins) {
355         this.failedLogins = failedLogins;
356     }
357 
358     @Override
359     public OffsetDateTime getLastLoginDate() {
360         return lastLoginDate;
361     }
362 
363     @Override
364     public void setLastLoginDate(final OffsetDateTime lastLoginDate) {
365         this.lastLoginDate = lastLoginDate;
366     }
367 
368     @Override
369     public String getUsername() {
370         return username;
371     }
372 
373     @Override
374     public void setUsername(final String username) {
375         this.username = username;
376     }
377 
378     @Override
379     public void setSuspended(final Boolean suspended) {
380         this.suspended = suspended;
381     }
382 
383     @Override
384     public Boolean isSuspended() {
385         return suspended;
386     }
387 
388     @Override
389     public void setMustChangePassword(final boolean mustChangePassword) {
390         this.mustChangePassword = mustChangePassword;
391     }
392 
393     @Override
394     public boolean isMustChangePassword() {
395         return mustChangePassword;
396     }
397 
398     @Override
399     public SecurityQuestion getSecurityQuestion() {
400         return securityQuestion;
401     }
402 
403     @Override
404     public void setSecurityQuestion(final SecurityQuestion securityQuestion) {
405         checkType(securityQuestion, JPASecurityQuestion.class);
406         this.securityQuestion = (JPASecurityQuestion) securityQuestion;
407     }
408 
409     @Override
410     public String getSecurityAnswer() {
411         return securityAnswer;
412     }
413 
414     @Override
415     public void setSecurityAnswer(final String securityAnswer) {
416         try {
417             this.securityAnswer = ENCRYPTOR.encode(securityAnswer, cipherAlgorithm == null
418                     ? CipherAlgorithm.valueOf(ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).
419                             get(AuthContextUtils.getDomain(), "password.cipher.algorithm", CipherAlgorithm.AES.name(),
420                                     String.class))
421                     : cipherAlgorithm);
422         } catch (Exception e) {
423             LOG.error("Could not encode security answer", e);
424             this.securityAnswer = null;
425         }
426     }
427 
428     @Override
429     public boolean add(final AnyTypeClass auxClass) {
430         checkType(auxClass, JPAAnyTypeClass.class);
431         return auxClasses.contains((JPAAnyTypeClass) auxClass) || auxClasses.add((JPAAnyTypeClass) auxClass);
432     }
433 
434     @Override
435     public List<? extends AnyTypeClass> getAuxClasses() {
436         return auxClasses;
437     }
438 
439     @Override
440     public boolean add(final URelationship relationship) {
441         checkType(relationship, JPAURelationship.class);
442         return this.relationships.add((JPAURelationship) relationship);
443     }
444 
445     @Override
446     public Optional<? extends URelationship> getRelationship(
447             final RelationshipType relationshipType, final String otherEndKey) {
448 
449         return getRelationships().stream().filter(relationship -> relationshipType.equals(relationship.getType())
450                 && otherEndKey != null && otherEndKey.equals(relationship.getRightEnd().getKey())).
451                 findFirst();
452     }
453 
454     @Override
455     public List<? extends URelationship> getRelationships() {
456         return relationships;
457     }
458 
459     @Override
460     public boolean add(final UMembership membership) {
461         checkType(membership, JPAUMembership.class);
462         return this.memberships.add((JPAUMembership) membership);
463     }
464 
465     @Override
466     public boolean remove(final UMembership membership) {
467         checkType(membership, JPAUMembership.class);
468         return this.memberships.remove((JPAUMembership) membership);
469     }
470 
471     @Override
472     public List<? extends UMembership> getMemberships() {
473         return memberships;
474     }
475 
476     @Override
477     public boolean add(final LinkedAccount account) {
478         checkType(account, JPALinkedAccount.class);
479         return linkedAccounts.contains((JPALinkedAccount) account) || linkedAccounts.add((JPALinkedAccount) account);
480     }
481 
482     @Override
483     public Optional<? extends LinkedAccount> getLinkedAccount(final String resource, final String connObjectKeyValue) {
484         return linkedAccounts.stream().
485                 filter(account -> account.getResource().getKey().equals(resource)
486                 && account.getConnObjectKeyValue().equals(connObjectKeyValue)).
487                 findFirst();
488     }
489 
490     @Override
491     public List<? extends LinkedAccount> getLinkedAccounts(final String resource) {
492         return linkedAccounts.stream().
493                 filter(account -> account.getResource().getKey().equals(resource)).
494                 collect(Collectors.toList());
495     }
496 
497     @Override
498     public List<? extends LinkedAccount> getLinkedAccounts() {
499         return linkedAccounts;
500     }
501 }