/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.persistence.jpa.entity.user;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.jpa.entity.resource.JPAExternalResource;
import org.apache.syncope.core.persistence.jpa.entity.JPASecurityQuestion;
import org.apache.syncope.core.spring.security.Encryptor;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.entity.AnyType;
import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
import org.apache.syncope.core.persistence.api.entity.Role;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.URelationship;
import org.apache.syncope.core.persistence.jpa.entity.AbstractGroupableRelatable;
import org.apache.syncope.core.persistence.jpa.entity.JPAAnyTypeClass;
import org.apache.syncope.core.persistence.jpa.entity.JPARole;
@Entity
@Table(name = JPAUser.TABLE)
@Cacheable
public class JPAUser
extends AbstractGroupableRelatable<User, UMembership, UPlainAttr, AnyObject, URelationship>
implements User {
private static final long serialVersionUID = -3905046855521446823L;
public static final String TABLE = "SyncopeUser";
@Column(nullable = true)
private String password;
@Transient
private String clearPassword;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(joinColumns =
@JoinColumn(name = "user_id"),
inverseJoinColumns =
@JoinColumn(name = "role_id"))
private List<JPARole> roles = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
@Valid
private List<JPAUPlainAttr> plainAttrs = new ArrayList<>();
private String workflowId;
@Column(nullable = true)
private String status;
@Lob
private String token;
@Temporal(TemporalType.TIMESTAMP)
private Date tokenExpireTime;
@Column(nullable = true)
@Enumerated(EnumType.STRING)
private CipherAlgorithm cipherAlgorithm;
@ElementCollection
@Column(name = "passwordHistoryValue")
@CollectionTable(name = "SyncopeUser_passwordHistory", joinColumns =
@JoinColumn(name = "user_id", referencedColumnName = "id"))
private List<String> passwordHistory = new ArrayList<>();
/**
* Subsequent failed logins.
*/
@Column(nullable = true)
private Integer failedLogins;
/**
* Username/Login.
*/
@Column(unique = true)
@NotNull(message = "Blank username")
private String username;
/**
* Last successful login date.
*/
@Column(nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date lastLoginDate;
/**
* Change password date.
*/
@Column(nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date changePwdDate;
@Column(nullable = true)
private String lastRecertificator;
@Column(nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date lastRecertification;
@Basic
@Min(0)
@Max(1)
private Integer suspended = getBooleanAsInteger(Boolean.FALSE);
@Basic
@Min(0)
@Max(1)
private Integer mustChangePassword = getBooleanAsInteger(Boolean.FALSE);
/**
* Provisioning external resources.
*/
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(joinColumns =
@JoinColumn(name = "user_id"),
inverseJoinColumns =
@JoinColumn(name = "resource_id"))
private List<JPAExternalResource> resources = new ArrayList<>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(joinColumns =
@JoinColumn(name = "user_id"),
inverseJoinColumns =
@JoinColumn(name = "anyTypeClass_id"))
private List<JPAAnyTypeClass> auxClasses = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "leftEnd")
@Valid
private List<JPAURelationship> relationships = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "leftEnd")
@Valid
private List<JPAUMembership> memberships = new ArrayList<>();
@ManyToOne(fetch = FetchType.EAGER)
private JPASecurityQuestion securityQuestion;
@Column(nullable = true)
private String securityAnswer;
@Override
public AnyType getType() {
return ApplicationContextProvider.getBeanFactory().getBean(AnyTypeDAO.class).findUser();
}
@Override
public void setType(final AnyType type) {
// nothing to do
}
@Override
public boolean add(final ExternalResource resource) {
checkType(resource, JPAExternalResource.class);
return resources.add((JPAExternalResource) resource);
}
@Override
public List<? extends ExternalResource> getResources() {
return resources;
}
@Override
public boolean add(final Role role) {
checkType(role, JPARole.class);
return roles.contains((JPARole) role) || roles.add((JPARole) role);
}
@Override
public List<? extends Role> getRoles() {
return roles;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getClearPassword() {
return clearPassword;
}
public void setClearPassword(final String clearPassword) {
this.clearPassword = clearPassword;
}
@Override
public void removeClearPassword() {
setClearPassword(null);
}
@Override
public void setEncodedPassword(final String password, final CipherAlgorithm cipherAlgoritm) {
this.clearPassword = null;
this.password = password;
this.cipherAlgorithm = cipherAlgoritm;
setMustChangePassword(false);
}
@Override
public void setPassword(final String password, final CipherAlgorithm cipherAlgoritm) {
this.clearPassword = password;
try {
this.password = Encryptor.getInstance().encode(password, cipherAlgoritm);
this.cipherAlgorithm = cipherAlgoritm;
} catch (Exception e) {
LOG.error("Could not encode password", e);
this.password = null;
}
}
@Override
public CipherAlgorithm getCipherAlgorithm() {
return cipherAlgorithm;
}
@Override
public boolean canDecodePassword() {
return this.cipherAlgorithm != null && this.cipherAlgorithm.isInvertible();
}
@Override
public boolean add(final UPlainAttr attr) {
checkType(attr, JPAUPlainAttr.class);
return plainAttrs.add((JPAUPlainAttr) attr);
}
@Override
protected List<? extends UPlainAttr> internalGetPlainAttrs() {
return plainAttrs;
}
@Override
public String getWorkflowId() {
return workflowId;
}
@Override
public void setWorkflowId(final String workflowId) {
this.workflowId = workflowId;
}
@Override
public String getStatus() {
return status;
}
@Override
public void setStatus(final String status) {
this.status = status;
}
@Override
public void generateToken(final int tokenLength, final int tokenExpireTime) {
this.token = SecureRandomUtils.generateRandomPassword(tokenLength);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, tokenExpireTime);
this.tokenExpireTime = calendar.getTime();
}
@Override
public void removeToken() {
this.token = null;
this.tokenExpireTime = null;
}
@Override
public String getToken() {
return token;
}
@Override
public Date getTokenExpireTime() {
return tokenExpireTime == null
? null
: new Date(tokenExpireTime.getTime());
}
@Override
public boolean checkToken(final String token) {
return this.token == null
? token == null
: this.token.equals(token) && !hasTokenExpired();
}
@Override
public boolean hasTokenExpired() {
return tokenExpireTime == null
? false
: tokenExpireTime.before(new Date());
}
@Override
public void setCipherAlgorithm(final CipherAlgorithm cipherAlgorithm) {
this.cipherAlgorithm = cipherAlgorithm;
}
@Override
public List<String> getPasswordHistory() {
return passwordHistory;
}
@Override
public Date getChangePwdDate() {
return changePwdDate == null
? null
: new Date(changePwdDate.getTime());
}
@Override
public void setChangePwdDate(final Date changePwdDate) {
this.changePwdDate = changePwdDate == null
? null
: new Date(changePwdDate.getTime());
}
@Override
public Integer getFailedLogins() {
return failedLogins == null ? Integer.valueOf(0) : failedLogins;
}
@Override
public void setFailedLogins(final Integer failedLogins) {
this.failedLogins = failedLogins;
}
@Override
public Date getLastLoginDate() {
return lastLoginDate == null
? null
: new Date(lastLoginDate.getTime());
}
@Override
public void setLastLoginDate(final Date lastLoginDate) {
this.lastLoginDate = lastLoginDate == null
? null
: new Date(lastLoginDate.getTime());
}
@Override
public String getLastRecertificator() {
return lastRecertificator;
}
@Override
public void setLastRecertificator(final String lastRecertificator) {
this.lastRecertificator = lastRecertificator;
}
@Override
public Date getLastRecertification() {
if (lastRecertification != null) {
return new Date(lastRecertification.getTime());
}
return null;
}
@Override
public void setLastRecertification(final Date lastRecertification) {
if (lastRecertification != null) {
this.lastRecertification = new Date(lastRecertification.getTime());
} else {
this.lastRecertification = null;
}
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(final String username) {
this.username = username;
}
@Override
public void setSuspended(final Boolean suspended) {
this.suspended = getBooleanAsInteger(suspended);
}
@Override
public Boolean isSuspended() {
return suspended == null ? null : isBooleanAsInteger(suspended);
}
@Override
public void setMustChangePassword(final boolean mustChangePassword) {
this.mustChangePassword = getBooleanAsInteger(mustChangePassword);
}
@Override
public boolean isMustChangePassword() {
return isBooleanAsInteger(mustChangePassword);
}
@Override
public boolean verifyPasswordHistory(final String password, final int size) {
boolean res = false;
if (size > 0) {
try {
res = passwordHistory.subList(size >= passwordHistory.size()
? 0
: passwordHistory.size() - size, passwordHistory.size()).contains(cipherAlgorithm == null
? password
: Encryptor.getInstance().encode(password, cipherAlgorithm));
} catch (Exception e) {
LOG.error("Error evaluating password history", e);
}
}
return res;
}
@Override
public SecurityQuestion getSecurityQuestion() {
return securityQuestion;
}
@Override
public void setSecurityQuestion(final SecurityQuestion securityQuestion) {
checkType(securityQuestion, JPASecurityQuestion.class);
this.securityQuestion = (JPASecurityQuestion) securityQuestion;
}
@Override
public String getSecurityAnswer() {
return securityAnswer;
}
@Override
public void setSecurityAnswer(final String securityAnswer) {
this.securityAnswer = securityAnswer;
}
@Override
public boolean add(final AnyTypeClass auxClass) {
checkType(auxClass, JPAAnyTypeClass.class);
return this.auxClasses.add((JPAAnyTypeClass) auxClass);
}
@Override
public List<? extends AnyTypeClass> getAuxClasses() {
return auxClasses;
}
@Override
public boolean add(final URelationship relationship) {
checkType(relationship, JPAURelationship.class);
return this.relationships.add((JPAURelationship) relationship);
}
@Override
public List<? extends URelationship> getRelationships() {
return relationships;
}
@Override
public boolean add(final UMembership membership) {
checkType(membership, JPAUMembership.class);
return this.memberships.add((JPAUMembership) membership);
}
@Override
public List<? extends UMembership> getMemberships() {
return memberships;
}
}