User manager to hash with SHA-384
[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 javax.xml.bind.annotation.XmlAccessType;
22 import javax.xml.bind.annotation.XmlAccessorType;
23 import javax.xml.bind.annotation.XmlElement;
24 import javax.xml.bind.annotation.XmlRootElement;
25
26 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
27 import org.opendaylight.controller.sal.utils.HexEncode;
28 import org.opendaylight.controller.sal.utils.Status;
29 import org.opendaylight.controller.sal.utils.StatusCode;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Configuration Java Object which represents a Local AAA user configuration
35  * information for User Manager.
36  */
37 @XmlRootElement
38 @XmlAccessorType(XmlAccessType.NONE)
39 public class UserConfig implements Serializable {
40     private static final long serialVersionUID = 1L;
41     private static Logger log = LoggerFactory.getLogger(UserConfig.class);
42     private static final boolean strongPasswordCheck = Boolean.getBoolean("enableStrongPasswordCheck");
43     private static final String DIGEST_ALGORITHM = "SHA-384";
44     private static final String BAD_PASSWORD = "Bad Password";
45     private static final int USERNAME_MAXLENGTH = 32;
46     protected static final String PASSWORD_REGEX = "(?=.*[^a-zA-Z0-9])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$";
47     private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)");
48     private static MessageDigest oneWayFunction;
49
50     static {
51         try {
52             UserConfig.oneWayFunction = MessageDigest.getInstance(DIGEST_ALGORITHM);
53         } catch (NoSuchAlgorithmException e) {
54             log.error(String.format("Implementation of %s digest algorithm not found: %s", DIGEST_ALGORITHM,
55                     e.getMessage()));
56         }
57     }
58
59     /**
60      * User Id
61      */
62     @XmlElement
63     protected String user;
64
65     /**
66      * List of roles a user can have
67      * example
68      * System-Admin
69      * Network-Admin
70      * Network-Operator
71      */
72     @XmlElement
73     protected List<String> roles;
74
75     /**
76      * Password
77      * Should be 8 to 256 characters long,
78      * contain both upper and lower case letters, at least one number,
79      * and at least one non alphanumeric character.
80      */
81     @XmlElement
82     private String password;
83
84
85
86     public UserConfig() {
87     }
88
89     /**
90      * Construct a UserConfig object and takes care of hashing the user password
91      *
92      * @param user
93      *            the user name
94      * @param password
95      *            the plain text password
96      * @param roles
97      *            the list of roles
98      */
99     public UserConfig(String user, String password, List<String> roles) {
100         this.user = user;
101
102         /*
103          * Password validation to be done on clear text password. If fails, mark
104          * the password with a well known label, so that object validation can
105          * report the proper error. Only if password is a valid one, hash it.
106          */
107         this.password = (validatePassword(password).isSuccess()) ? hash(password) : BAD_PASSWORD;
108
109         this.roles = (roles == null) ? new ArrayList<String>() : new ArrayList<String>(roles);
110     }
111
112     public String getUser() {
113         return user;
114     }
115
116     public String getPassword() {
117         return password;
118     }
119
120     public List<String> getRoles() {
121         return new ArrayList<String>(roles);
122     }
123
124     @Override
125     public int hashCode() {
126         final int prime = 31;
127         int result = 1;
128         result = prime * result
129                 + ((password == null) ? 0 : password.hashCode());
130         result = prime * result + ((roles == null) ? 0 : roles.hashCode());
131         result = prime * result + ((user == null) ? 0 : user.hashCode());
132         return result;
133     }
134
135     @Override
136     public boolean equals(Object obj) {
137         if (this == obj) {
138             return true;
139         }
140         if (obj == null) {
141             return false;
142         }
143         if (getClass() != obj.getClass()) {
144             return false;
145         }
146         UserConfig other = (UserConfig) obj;
147         if (password == null) {
148             if (other.password != null) {
149                 return false;
150             }
151         } else if (!password.equals(other.password)) {
152             return false;
153         }
154         if (roles == null) {
155             if (other.roles != null) {
156                 return false;
157             }
158         } else if (!roles.equals(other.roles)) {
159             return false;
160         }
161         if (user == null) {
162             if (other.user != null) {
163                 return false;
164             }
165         } else if (!user.equals(other.user)) {
166             return false;
167         }
168         return true;
169     }
170
171     @Override
172     public String toString() {
173         return "UserConfig[user=" + user + ", password=" + password + ", roles=" + roles +"]";
174     }
175
176     public Status validate() {
177         Status validCheck = validateUsername();
178         if (validCheck.isSuccess()) {
179             validCheck = (!password.equals(BAD_PASSWORD)) ? new Status(StatusCode.SUCCESS) : new Status(
180                     StatusCode.BADREQUEST,
181                     "Password should be 8 to 256 characters long, contain both upper and lower case letters, "
182                             + "at least one number and at least one non alphanumeric character");
183         }
184         if (validCheck.isSuccess()) {
185             validCheck = validateRoles();
186         }
187         return validCheck;
188     }
189
190     protected Status validateUsername() {
191         if (user == null || user.isEmpty()) {
192             return new Status(StatusCode.BADREQUEST, "Username cannot be empty");
193         }
194
195         Matcher mUser = UserConfig.INVALID_USERNAME_CHARACTERS.matcher(user);
196         if (user.length() > UserConfig.USERNAME_MAXLENGTH || mUser.find() == true) {
197             return new Status(StatusCode.BADREQUEST,
198                     "Username can have 1-32 non-whitespace "
199                             + "alphanumeric characters and any special "
200                             + "characters except ./#%;?\\");
201         }
202
203         return new Status(StatusCode.SUCCESS);
204     }
205
206     private Status validatePassword(String password) {
207         if (password == null || password.isEmpty()) {
208             return new Status(StatusCode.BADREQUEST, "Password cannot be empty");
209         }
210
211         if (strongPasswordCheck && !password.matches(UserConfig.PASSWORD_REGEX)) {
212             return new Status(StatusCode.BADREQUEST, "Password should be 8 to 256 characters long, "
213                     + "contain both upper and lower case letters, at least one number "
214                     + "and at least one non alphanumeric character");
215         }
216         return new Status(StatusCode.SUCCESS);
217     }
218
219     protected Status validateRoles() {
220         if (roles == null || roles.isEmpty()) {
221             return new Status(StatusCode.BADREQUEST, "No role specified");
222         }
223         return new Status(StatusCode.SUCCESS);
224     }
225
226     public Status update(String currentPassword, String newPassword, List<String> newRoles) {
227
228         // To make any changes to a user configured profile, current password
229         // must always be provided
230         if (!this.password.equals(hash(currentPassword))) {
231             return new Status(StatusCode.BADREQUEST, "Current password is incorrect");
232         }
233
234         // Create a new object with the proposed modifications
235         UserConfig proposed = new UserConfig();
236         proposed.user = this.user;
237         proposed.password = (newPassword == null)? this.password : hash(newPassword);
238         proposed.roles = (newRoles == null)? this.roles : newRoles;
239
240         // Validate it
241         Status status = proposed.validate();
242         if (!status.isSuccess()) {
243             return status;
244         }
245
246         // Accept the modifications
247         this.user = proposed.user;
248         this.password = proposed.password;
249         this.roles = new ArrayList<String>(proposed.roles);
250
251         return status;
252     }
253
254     public AuthResponse authenticate(String clearTextPass) {
255         AuthResponse locResponse = new AuthResponse();
256         if (password.equals(hash(clearTextPass))) {
257             locResponse.setStatus(AuthResultEnum.AUTH_ACCEPT_LOC);
258             locResponse.addData(getRolesString());
259         } else {
260             locResponse.setStatus(AuthResultEnum.AUTH_REJECT_LOC);
261         }
262         return locResponse;
263     }
264
265     protected String getRolesString() {
266         StringBuffer buffer = new StringBuffer();
267         if (!roles.isEmpty()) {
268             Iterator<String> iter = roles.iterator();
269             buffer.append(iter.next());
270             while (iter.hasNext()) {
271                 buffer.append(" ");
272                 buffer.append(iter.next());
273             }
274         }
275         return buffer.toString();
276     }
277
278     public static String hash(String message) {
279         if (message == null) {
280             return message;
281         }
282         UserConfig.oneWayFunction.reset();
283         return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset())));
284     }
285
286     /**
287      * Returns UserConfig instance populated with the passed parameters. It does
288      * not run any checks on the passed parameters.
289      *
290      * @param userName
291      *            the user name
292      * @param password
293      *            the plain text password
294      * @param roles
295      *            the list of roles
296      * @return the UserConfig object populated with the passed parameters. No
297      *         validity check is run on the input parameters.
298      */
299     public static UserConfig getUncheckedUserConfig(String userName, String password, List<String> roles) {
300         UserConfig config = new UserConfig();
301         config.user = userName;
302         config.password = hash(password);
303         config.roles = roles;
304         return config;
305     }
306 }