Add strong password check for users
[controller.git] / opendaylight / usermanager / api / src / main / java / org / opendaylight / controller / usermanager / UserConfig.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.usermanager;
10
11 import java.io.Serializable;
12 import java.nio.charset.Charset;
13 import java.security.MessageDigest;
14 import java.security.NoSuchAlgorithmException;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
22 import org.opendaylight.controller.sal.utils.HexEncode;
23 import org.opendaylight.controller.sal.utils.Status;
24 import org.opendaylight.controller.sal.utils.StatusCode;
25 import org.opendaylight.controller.usermanager.AuthResponse;
26
27 /**
28  * Configuration Java Object which represents a Local AAA user configuration
29  * information for User Manager.
30  */
31 public class UserConfig implements Serializable {
32     private static final long serialVersionUID = 1L;
33
34     protected String user;
35     protected List<String> roles;
36     private String password;
37
38     private static final boolean strongPasswordCheck = Boolean.getBoolean("enableStrongPasswordCheck");
39     private static final String BAD_PASSWORD = "Bad Password";
40     private static final int USERNAME_MAXLENGTH = 32;
41     protected static final String PASSWORD_REGEX = "(?=.*[^\\w])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$";
42     private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)");
43     private static MessageDigest oneWayFunction = null;
44     static {
45         try {
46             UserConfig.oneWayFunction = MessageDigest.getInstance("SHA-1");
47         } catch (NoSuchAlgorithmException e) {
48             e.printStackTrace();
49         }
50     }
51
52     public UserConfig() {
53     }
54
55     /**
56      * Construct a UserConfig object and takes care of hashing the user password
57      *
58      * @param user
59      *            the user name
60      * @param password
61      *            the plain text password
62      * @param roles
63      *            the list of roles
64      */
65     public UserConfig(String user, String password, List<String> roles) {
66         this.user = user;
67
68         /*
69          * Password validation to be done on clear text password. If fails, mark
70          * the password with a well known label, so that object validation can
71          * report the proper error. Only if password is a valid one, hash it.
72          */
73         this.password = (validatePassword(password).isSuccess()) ? hash(password) : BAD_PASSWORD;
74
75         this.roles = (roles == null) ? new ArrayList<String>() : new ArrayList<String>(roles);
76     }
77
78     public String getUser() {
79         return user;
80     }
81
82     public String getPassword() {
83         return password;
84     }
85
86     public List<String> getRoles() {
87         return new ArrayList<String>(roles);
88     }
89
90     @Override
91     public int hashCode() {
92         final int prime = 31;
93         int result = 1;
94         result = prime * result
95                 + ((password == null) ? 0 : password.hashCode());
96         result = prime * result + ((roles == null) ? 0 : roles.hashCode());
97         result = prime * result + ((user == null) ? 0 : user.hashCode());
98         return result;
99     }
100
101     @Override
102     public boolean equals(Object obj) {
103         if (this == obj) {
104             return true;
105         }
106         if (obj == null) {
107             return false;
108         }
109         if (getClass() != obj.getClass()) {
110             return false;
111         }
112         UserConfig other = (UserConfig) obj;
113         if (password == null) {
114             if (other.password != null) {
115                 return false;
116             }
117         } else if (!password.equals(other.password)) {
118             return false;
119         }
120         if (roles == null) {
121             if (other.roles != null) {
122                 return false;
123             }
124         } else if (!roles.equals(other.roles)) {
125             return false;
126         }
127         if (user == null) {
128             if (other.user != null) {
129                 return false;
130             }
131         } else if (!user.equals(other.user)) {
132             return false;
133         }
134         return true;
135     }
136
137     @Override
138     public String toString() {
139         return "UserConfig[user=" + user + ", password=" + password + ", roles=" + roles +"]";
140     }
141
142     public Status validate() {
143         Status validCheck = validateUsername();
144         if (validCheck.isSuccess()) {
145             validCheck = (!password.equals(BAD_PASSWORD)) ? new Status(StatusCode.SUCCESS) : new Status(
146                     StatusCode.BADREQUEST,
147                     "Password should be 8 to 256 characters long, contain both upper and lower case letters, "
148                             + "at least one number and at least one non alphanumeric character");
149         }
150         if (validCheck.isSuccess()) {
151             validCheck = validateRoles();
152         }
153         return validCheck;
154     }
155
156     protected Status validateUsername() {
157         if (user == null || user.isEmpty()) {
158             return new Status(StatusCode.BADREQUEST, "Username cannot be empty");
159         }
160
161         Matcher mUser = UserConfig.INVALID_USERNAME_CHARACTERS.matcher(user);
162         if (user.length() > UserConfig.USERNAME_MAXLENGTH || mUser.find() == true) {
163             return new Status(StatusCode.BADREQUEST,
164                     "Username can have 1-32 non-whitespace "
165                             + "alphanumeric characters and any special "
166                             + "characters except ./#%;?\\");
167         }
168
169         return new Status(StatusCode.SUCCESS);
170     }
171
172     private Status validatePassword(String password) {
173         if (password == null || password.isEmpty()) {
174             return new Status(StatusCode.BADREQUEST, "Password cannot be empty");
175         }
176
177         if (strongPasswordCheck && !password.matches(UserConfig.PASSWORD_REGEX)) {
178             return new Status(StatusCode.BADREQUEST, "Password should be 8 to 256 characters long, "
179                     + "contain both upper and lower case letters, at least one number "
180                     + "and at least one non alphanumeric character");
181         }
182         return new Status(StatusCode.SUCCESS);
183     }
184
185     protected Status validateRoles() {
186         if (roles == null || roles.isEmpty()) {
187             return new Status(StatusCode.BADREQUEST, "No role specified");
188         }
189         return new Status(StatusCode.SUCCESS);
190     }
191
192     public Status update(String currentPassword, String newPassword, List<String> newRoles) {
193
194         // To make any changes to a user configured profile, current password
195         // must always be provided
196         if (!this.password.equals(hash(currentPassword))) {
197             return new Status(StatusCode.BADREQUEST, "Current password is incorrect");
198         }
199
200         // Create a new object with the proposed modifications
201         UserConfig proposed = new UserConfig();
202         proposed.user = this.user;
203         proposed.password = (newPassword == null)? this.password : hash(newPassword);
204         proposed.roles = (newRoles == null)? this.roles : newRoles;
205
206         // Validate it
207         Status status = proposed.validate();
208         if (!status.isSuccess()) {
209             return status;
210         }
211
212         // Accept the modifications
213         this.user = proposed.user;
214         this.password = proposed.password;
215         this.roles = new ArrayList<String>(proposed.roles);
216
217         return status;
218     }
219
220     public AuthResponse authenticate(String clearTextPass) {
221         AuthResponse locResponse = new AuthResponse();
222         if (password.equals(hash(clearTextPass))) {
223             locResponse.setStatus(AuthResultEnum.AUTH_ACCEPT_LOC);
224             locResponse.addData(getRolesString());
225         } else {
226             locResponse.setStatus(AuthResultEnum.AUTH_REJECT_LOC);
227         }
228         return locResponse;
229     }
230
231     protected String getRolesString() {
232         StringBuffer buffer = new StringBuffer();
233         if (!roles.isEmpty()) {
234             Iterator<String> iter = roles.iterator();
235             buffer.append(iter.next());
236             while (iter.hasNext()) {
237                 buffer.append(" ");
238                 buffer.append(iter.next());
239             }
240         }
241         return buffer.toString();
242     }
243
244     public static String hash(String message) {
245         if (message == null) {
246             return message;
247         }
248         UserConfig.oneWayFunction.reset();
249         return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset())));
250     }
251
252     /**
253      * Returns UserConfig instance populated with the passed parameters. It does
254      * not run any checks on the passed parameters.
255      *
256      * @param userName
257      *            the user name
258      * @param password
259      *            the plain text password
260      * @param roles
261      *            the list of roles
262      * @return the UserConfig object populated with the passed parameters. No
263      *         validity check is run on the input parameters.
264      */
265     public static UserConfig getUncheckedUserConfig(String userName, String password, List<String> roles) {
266         UserConfig config = new UserConfig();
267         config.user = userName;
268         config.password = hash(password);
269         config.roles = roles;
270         return config;
271     }
272 }