2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.usermanager;
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;
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;
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;
34 * Configuration Java Object which represents a Local AAA user configuration
35 * information for User Manager.
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;
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,
63 protected String user;
66 * List of roles a user can have
73 protected List<String> roles;
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.
82 private String password;
90 * Construct a UserConfig object and takes care of hashing the user password
95 * the plain text password
99 public UserConfig(String user, String password, List<String> roles) {
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.
107 this.password = (validatePassword(password).isSuccess()) ? hash(password) : BAD_PASSWORD;
109 this.roles = (roles == null) ? new ArrayList<String>() : new ArrayList<String>(roles);
112 public String getUser() {
116 public String getPassword() {
120 public List<String> getRoles() {
121 return new ArrayList<String>(roles);
125 public int hashCode() {
126 final int prime = 31;
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());
136 public boolean equals(Object obj) {
143 if (getClass() != obj.getClass()) {
146 UserConfig other = (UserConfig) obj;
147 if (password == null) {
148 if (other.password != null) {
151 } else if (!password.equals(other.password)) {
155 if (other.roles != null) {
158 } else if (!roles.equals(other.roles)) {
162 if (other.user != null) {
165 } else if (!user.equals(other.user)) {
172 public String toString() {
173 return "UserConfig[user=" + user + ", password=" + password + ", roles=" + roles +"]";
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");
184 if (validCheck.isSuccess()) {
185 validCheck = validateRoles();
190 protected Status validateUsername() {
191 if (user == null || user.isEmpty()) {
192 return new Status(StatusCode.BADREQUEST, "Username cannot be empty");
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 ./#%;?\\");
203 return new Status(StatusCode.SUCCESS);
206 private Status validatePassword(String password) {
207 if (password == null || password.isEmpty()) {
208 return new Status(StatusCode.BADREQUEST, "Password cannot be empty");
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");
216 return new Status(StatusCode.SUCCESS);
219 protected Status validateRoles() {
220 if (roles == null || roles.isEmpty()) {
221 return new Status(StatusCode.BADREQUEST, "No role specified");
223 return new Status(StatusCode.SUCCESS);
226 public Status update(String currentPassword, String newPassword, List<String> newRoles) {
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");
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;
241 Status status = proposed.validate();
242 if (!status.isSuccess()) {
246 // Accept the modifications
247 this.user = proposed.user;
248 this.password = proposed.password;
249 this.roles = new ArrayList<String>(proposed.roles);
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());
260 locResponse.setStatus(AuthResultEnum.AUTH_REJECT_LOC);
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()) {
272 buffer.append(iter.next());
275 return buffer.toString();
278 public static String hash(String message) {
279 if (message == null) {
282 UserConfig.oneWayFunction.reset();
283 return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset())));
287 * Returns UserConfig instance populated with the passed parameters. It does
288 * not run any checks on the passed parameters.
293 * the plain text password
296 * @return the UserConfig object populated with the passed parameters. No
297 * validity check is run on the input parameters.
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;