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