blob: 59932fb9e43d050e8c1f8dad85493cd96df25d0b [file] [log] [blame]
/*
* 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.jsecurity.samples.sprhib.party;
import org.jsecurity.authc.Account;
import org.jsecurity.authz.Permission;
import org.jsecurity.samples.sprhib.security.Role;
import org.jsecurity.subject.PrincipalCollection;
import org.jsecurity.subject.SimplePrincipalCollection;
import java.text.DateFormat;
import java.util.*;
import java.util.regex.Pattern;
/**
* Simple class that represents any User domain entity in any application. It extends the {@link Person Person} class
* to show a non-trivial class hierarchy, since such hierarchies exist in most Hibernate applications in one form
* or another. Naturally you could ignore the parent class in your application, but it does represent a clean
* OO way of modeling things.
*
* <p>This class implements the {@link org.jsecurity.authc.Account} interface for dead-simple integration
* with JSecurity - this allows you to use your User objects directly inside of
* {@link org.jsecurity.realm.Realm Realm} implementations, significantly reducing the implementation effort.</p>
*
* <p>Because this class performs its own Realm and Permission checks, and these can happen frequently enough in a
* production application, it is highly recommended that the internal User {@link #getUserRoles} collection be cached
* in a 2nd-level cache when using JPA and/or Hibernate. The hibernate xml configuration for this sample application
* does in fact do this for your reference (see User.hbm.xml - the 'roles' declaration).</p>
*
* <p>If you ever decide not to use JSecurity, the only domain change would be to simply remove the
* <code>Account</code> interface declaration</p>
*
* @author Les Hazlewood
*/
public class User extends Person implements Account {
/**
* Requires 6 or more alphanumeric and/or punctuation characters.
*/
public static final Pattern VALID_PASSWORD_PATTERN = Pattern.compile("[\\p{Alnum}\\p{Punct}]{6,255}");
public static final Pattern VALID_USERNAME_PATTERN = Pattern.compile("[\\p{Alnum}_-]{1,255}");
public static final String ROOT_USER_USERNAME = "root";
private String username;
private String password;
private String passwordResetKey; //UUID generated when they ask to reset the password
private Date passwordResetKeyTimestamp; //when they asked to reset the password
private Date lastLoginTimestamp; //can be null if never logged in
private Date lockTimestamp; //date the account was locked, null means unlocked (default behavior)
private boolean sessionTimeoutEnabled = true; //per-user session configuration
private Set<Role> roles;
public User() {
}
/**
* Returns the username associated with this user account;
*
* @return the username associated with this user account;
*/
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the password for this user.
*
* @return this user's password
*/
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* If the user forgets their password, this key is set first. If they then request to reset their password, they
* must submit this key for the reset request to be valid. Otherwise, the password reset is timed out after a
* certain amount of time after {@link #getPasswordResetKeyTimestamp() passwordResetKeyTimestamp}
*
* @return
*/
public String getPasswordResetKey() {
return passwordResetKey;
}
public void setPasswordResetKey(String passwordResetKey) {
this.passwordResetKey = passwordResetKey;
}
public Date getPasswordResetKeyTimestamp() {
return passwordResetKeyTimestamp;
}
public void setPasswordResetKeyTimestamp(Date passwordResetKeyTimestamp) {
this.passwordResetKeyTimestamp = passwordResetKeyTimestamp;
}
/**
* Returns the timestamp this User last logged in successfully to the application, or
* <tt>null</tt> if the user has never logged in.
*
* @return the timestamp this User last logged in successfully to the application, or
* <tt>null</tt> if the user has never logged in.
*/
public Date getLastLoginTimestamp() {
return lastLoginTimestamp;
}
/**
* Sets the timestamp this User last logged in successfully to the application.
*
* @param lastLoginTimestamp the timestamp this User last logged in successfully to the
* application.
*/
public void setLastLoginTimestamp(Date lastLoginTimestamp) {
this.lastLoginTimestamp = lastLoginTimestamp;
}
/**
* Returns the time when this account was locked, either due to too many login attempts, an
* explicit lock-out by an administrator, or because of some other security reason. <p>This
* method returns <tt>null</tt> if the account is not locked and is considered to be in good
* standing</p>
*
* @return the time when this account was locked, or <tt>null</tt> if this account is not locked
* and is considered to be in good standing.
*/
public Date getLockTimestamp() {
return lockTimestamp;
}
public void setLockTimestamp(Date lockTimestamp) {
this.lockTimestamp = lockTimestamp;
}
/**
* Returns whether or not this particular user account can expire due to inactivity. <p>Defaults
* to <tt>true</tt> as almost all user accounts should expire due to inactivity.
*
* @return <tt>true</tt> if this user's sessions can timeout due to inactivity, <tt>false</tt>
* otherwise.
*/
public boolean isSessionTimeoutEnabled() {
return sessionTimeoutEnabled;
}
public void setSessionTimeoutEnabled(boolean sessionTimeoutEnabled) {
this.sessionTimeoutEnabled = sessionTimeoutEnabled;
}
/**
* Returns whether or not this user account is locked, thereby preventing further log-ins.
*
* @return <tt>true</tt> if this user account is locked and will not be allowed to log-in,
* <tt>false</tt>
*/
public boolean isLocked() {
return getLockTimestamp() != null;
}
/**
* Convenience method for updating the state to locked.
*
* @param locked whether or not this user account will be locked.
* @see #getLockTimestamp()
*/
public void setLocked(boolean locked) {
if (locked) {
if (getLockTimestamp() == null) {
setLockTimestamp(new Date());
}
} else {
setLockTimestamp(null);
}
}
public Set<Role> getUserRoles() {
return roles;
}
public void setUserRoles(Set<Role> roles) {
this.roles = roles;
}
public Role getRole(String name) {
Collection<Role> roles = getUserRoles();
if (roles != null && !roles.isEmpty()) {
for (Role role : roles) {
if (role.getName().equals(name)) {
return role;
}
}
}
return null;
}
/**
* Adds a Role to this user's collection of {@link #getRoles() roles}.
*
* <p>If the existing roles collection is <tt>null</tt>, a new collection will be created and
* assigned to this User and then the Role will be added.
*
* @param r the Role to add/associate with this User
*/
public void add(Role r) {
Set<Role> roles = getUserRoles();
if (roles == null) {
roles = new LinkedHashSet<Role>();
setUserRoles(roles);
}
roles.add(r);
}
public boolean removeRole(Role r) {
Set<Role> roles = getUserRoles();
return roles != null && roles.remove(r);
}
protected String getPrivateRoleName(PrincipalCollection principals) {
return getClass().getName() + "_PRIVATE_ROLE_" + PrincipalCollection.class.getName();
}
protected Role createPrivateRole(PrincipalCollection principals) {
String privateRoleName = getPrivateRoleName(principals);
return new Role(privateRoleName, this);
}
public Set<Permission> getPermissions() {
Set<Permission> permissions = new HashSet<Permission>();
for (Role role : roles) {
permissions.addAll(role.getPermissions());
}
return permissions;
}
public Set<String> getRolenames() {
Set<String> rolenames = new HashSet<String>();
for (Role role : roles) {
rolenames.add(role.getName());
}
return rolenames;
}
public void addRole(String roleName) {
Role existing = getRole(roleName);
if (existing == null) {
Role role = new Role(roleName);
add(role);
}
}
public void addRoles(Set<String> roleNames) {
if (roleNames != null && !roleNames.isEmpty()) {
for (String name : roleNames) {
addRole(name);
}
}
}
public void addAll(Collection<Role> roles) {
if (roles != null && !roles.isEmpty()) {
Set<Role> existingRoles = getUserRoles();
if (existingRoles == null) {
existingRoles = new LinkedHashSet<Role>(roles.size());
setUserRoles(existingRoles);
}
existingRoles.addAll(roles);
}
}
public static boolean isValidPassword(String password) {
return password != null && VALID_PASSWORD_PATTERN.matcher(password).matches();
}
public static boolean isValidUsername(String username) {
return username != null && VALID_USERNAME_PATTERN.matcher(username).matches();
}
public StringBuffer toStringBuffer() {
StringBuffer sb = super.toStringBuffer();
sb.append(",username=").append(getUsername());
sb.append(",password=<protected>");
DateFormat df = DateFormat.getInstance();
Date ts = getLastLoginTimestamp();
if (ts != null) {
sb.append(",lastLoginTimestamp=").append(df.format(ts));
}
ts = getLockTimestamp();
if (ts != null) {
sb.append(",lockTimestamp=").append(df.format(ts));
}
sb.append(",sessionTimeoutEnabled=").append(isSessionTimeoutEnabled());
return sb;
}
public boolean onEquals(Object o) {
if (o instanceof User) {
User u = (User) o;
return getUsername().equals(u.getUsername());
}
return false;
}
public int hashCode() {
return getUsername().hashCode();
}
@Override
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
public Object clone() {
User clone = (User) super.clone();
clone.setUsername(getUsername());
clone.setPassword(getPassword());
clone.setPasswordResetKey(getPasswordResetKey());
clone.setPasswordResetKeyTimestamp(getPasswordResetKeyTimestamp());
clone.setLastLoginTimestamp(getLastLoginTimestamp());
clone.setLockTimestamp(getLockTimestamp());
clone.setSessionTimeoutEnabled(isSessionTimeoutEnabled());
return clone;
}
public static void main(String[] args) {
String username = "s-ls";
if (!isValidUsername(username)) {
System.out.println("Not a valid username!");
} else {
System.out.println("Valid username.");
}
}
/* ===========
JSecurity Account implementations below here.
=========== */
public PrincipalCollection getPrincipals() {
//The realm name must match the name of the configured realm.
return new SimplePrincipalCollection(getId(), "DefaultRealm");
}
public Object getCredentials() {
return getPassword();
}
public boolean isCredentialsExpired() {
//if applications wanted to expire passwords after a certain amount of time, this method would calculate
//true or false based on the current time and a passwordLastUpdateTimestamp;
//this sample app doesn't use this feature, so just return false always:
return false;
}
public Collection<String> getRoles() {
return getRolenames();
}
public Collection<String> getStringPermissions() {
// This model uses object permissions, so this method isn't implemented
return null;
}
public Collection<Permission> getObjectPermissions() {
Set<Permission> permissions = new HashSet<Permission>();
for (Role role : getUserRoles()) {
permissions.addAll(role.getPermissions());
}
return permissions;
}
}