cca194e95337e30877aaff50332e37e6df4be30f
[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     private static final int USERNAME_MAXLENGTH = 32;
38     private static final int PASSWORD_MINLENGTH = 5;
39     private static final int PASSWORD_MAXLENGTH = 256;
40     private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)");
41     private static MessageDigest oneWayFunction = null;
42     static {
43         try {
44             UserConfig.oneWayFunction = MessageDigest.getInstance("SHA-1");
45         } catch (NoSuchAlgorithmException e) {
46             e.printStackTrace();
47         }
48     }
49
50     public UserConfig() {
51     }
52
53     /**
54      * Construct a UserConfig object and takes care of hashing the user password
55      *
56      * @param user
57      *            the user name
58      * @param password
59      *            the plain text password
60      * @param roles
61      *            the list of roles
62      */
63     public UserConfig(String user, String password, List<String> roles) {
64         this.user = user;
65
66         this.password = password;
67         if (this.validatePassword().isSuccess()) {
68             /*
69              * Only if the password is a valid one, hash it. So in case it is not
70              * valid, when UserConfig.validate() is called, the proper
71              * validation error will be returned to the caller. If we hashed a
72              * priori instead, the mis-configuration would be masked
73              */
74             this.password = hash(this.password);
75         }
76
77         this.roles = (roles == null) ? new ArrayList<String>() : new ArrayList<String>(roles);
78     }
79
80     public String getUser() {
81         return user;
82     }
83
84     public String getPassword() {
85         return password;
86     }
87
88     public List<String> getRoles() {
89         return new ArrayList<String>(roles);
90     }
91
92     @Override
93     public int hashCode() {
94         final int prime = 31;
95         int result = 1;
96         result = prime * result
97                 + ((password == null) ? 0 : password.hashCode());
98         result = prime * result + ((roles == null) ? 0 : roles.hashCode());
99         result = prime * result + ((user == null) ? 0 : user.hashCode());
100         return result;
101     }
102
103     @Override
104     public boolean equals(Object obj) {
105         if (this == obj) {
106             return true;
107         }
108         if (obj == null) {
109             return false;
110         }
111         if (getClass() != obj.getClass()) {
112             return false;
113         }
114         UserConfig other = (UserConfig) obj;
115         if (password == null) {
116             if (other.password != null) {
117                 return false;
118             }
119         } else if (!password.equals(other.password)) {
120             return false;
121         }
122         if (roles == null) {
123             if (other.roles != null) {
124                 return false;
125             }
126         } else if (!roles.equals(other.roles)) {
127             return false;
128         }
129         if (user == null) {
130             if (other.user != null) {
131                 return false;
132             }
133         } else if (!user.equals(other.user)) {
134             return false;
135         }
136         return true;
137     }
138
139     @Override
140     public String toString() {
141         return "UserConfig[user=" + user + ", password=" + password + ", roles=" + roles +"]";
142     }
143
144     public Status validate() {
145         Status validCheck = validateRoles();
146         if (validCheck.isSuccess()) {
147             validCheck = validateUsername();
148         }
149         if (validCheck.isSuccess()) {
150             validCheck = validatePassword();
151         }
152         return validCheck;
153     }
154
155     protected Status validateUsername() {
156         if (user == null || user.isEmpty()) {
157             return new Status(StatusCode.BADREQUEST, "Username cannot be empty");
158         }
159
160         Matcher mUser = UserConfig.INVALID_USERNAME_CHARACTERS.matcher(user);
161         if (user.length() > UserConfig.USERNAME_MAXLENGTH || mUser.find() == true) {
162             return new Status(StatusCode.BADREQUEST,
163                     "Username can have 1-32 non-whitespace "
164                             + "alphanumeric characters and any special "
165                             + "characters except ./#%;?\\");
166         }
167
168         return new Status(StatusCode.SUCCESS);
169     }
170
171     private Status validatePassword() {
172         if (password == null || password.isEmpty()) {
173             return new Status(StatusCode.BADREQUEST, "Password cannot be empty");
174         }
175
176         if (password.length() < UserConfig.PASSWORD_MINLENGTH
177                 || password.length() > UserConfig.PASSWORD_MAXLENGTH) {
178             return new Status(StatusCode.BADREQUEST,
179                     "Password should have 5-256 characters");
180         }
181         return new Status(StatusCode.SUCCESS);
182     }
183
184     protected Status validateRoles() {
185         if (roles == null || roles.isEmpty()) {
186             return new Status(StatusCode.BADREQUEST, "No role specified");
187         }
188         return new Status(StatusCode.SUCCESS);
189     }
190
191     public Status update(String currentPassword, String newPassword, List<String> newRoles) {
192
193         // To make any changes to a user configured profile, current password
194         // must always be provided
195         if (!this.password.equals(hash(currentPassword))) {
196             return new Status(StatusCode.BADREQUEST, "Current password is incorrect");
197         }
198
199         // Create a new object with the proposed modifications
200         UserConfig proposed = new UserConfig();
201         proposed.user = this.user;
202         proposed.password = (newPassword == null)? this.password : hash(newPassword);
203         proposed.roles = (newRoles == null)? this.roles : newRoles;
204
205         // Validate it
206         Status status = proposed.validate();
207         if (!status.isSuccess()) {
208             return status;
209         }
210
211         // Accept the modifications
212         this.user = proposed.user;
213         this.password = proposed.password;
214         this.roles = new ArrayList<String>(proposed.roles);
215
216         return status;
217     }
218
219     public AuthResponse authenticate(String clearTextPass) {
220         AuthResponse locResponse = new AuthResponse();
221         if (password.equals(hash(clearTextPass))) {
222             locResponse.setStatus(AuthResultEnum.AUTH_ACCEPT_LOC);
223             locResponse.addData(getRolesString());
224         } else {
225             locResponse.setStatus(AuthResultEnum.AUTH_REJECT_LOC);
226         }
227         return locResponse;
228     }
229
230     protected String getRolesString() {
231         StringBuffer buffer = new StringBuffer();
232         if (!roles.isEmpty()) {
233             Iterator<String> iter = roles.iterator();
234             buffer.append(iter.next());
235             while (iter.hasNext()) {
236                 buffer.append(" ");
237                 buffer.append(iter.next());
238             }
239         }
240         return buffer.toString();
241     }
242
243     public static String hash(String message) {
244         if (message == null) {
245             return message;
246         }
247         UserConfig.oneWayFunction.reset();
248         return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset())));
249     }
250 }