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 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;
28 * Configuration Java Object which represents a Local AAA user configuration
29 * information for User Manager.
31 public class UserConfig implements Serializable {
32 private static final long serialVersionUID = 1L;
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;
44 UserConfig.oneWayFunction = MessageDigest.getInstance("SHA-1");
45 } catch (NoSuchAlgorithmException e) {
54 * Construct a UserConfig object and takes care of hashing the user password
59 * the plain text password
63 public UserConfig(String user, String password, List<String> roles) {
66 this.password = password;
67 if (this.validatePassword().isSuccess()) {
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
74 this.password = hash(this.password);
77 this.roles = (roles == null) ? new ArrayList<String>() : new ArrayList<String>(roles);
80 public String getUser() {
84 public String getPassword() {
88 public List<String> getRoles() {
89 return new ArrayList<String>(roles);
93 public int hashCode() {
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());
104 public boolean equals(Object obj) {
111 if (getClass() != obj.getClass()) {
114 UserConfig other = (UserConfig) obj;
115 if (password == null) {
116 if (other.password != null) {
119 } else if (!password.equals(other.password)) {
123 if (other.roles != null) {
126 } else if (!roles.equals(other.roles)) {
130 if (other.user != null) {
133 } else if (!user.equals(other.user)) {
140 public String toString() {
141 return "UserConfig[user=" + user + ", password=" + password + ", roles=" + roles +"]";
144 public Status validate() {
145 Status validCheck = validateRoles();
146 if (validCheck.isSuccess()) {
147 validCheck = validateUsername();
149 if (validCheck.isSuccess()) {
150 validCheck = validatePassword();
155 protected Status validateUsername() {
156 if (user == null || user.isEmpty()) {
157 return new Status(StatusCode.BADREQUEST, "Username cannot be empty");
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 ./#%;?\\");
168 return new Status(StatusCode.SUCCESS);
171 private Status validatePassword() {
172 if (password == null || password.isEmpty()) {
173 return new Status(StatusCode.BADREQUEST, "Password cannot be empty");
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");
181 return new Status(StatusCode.SUCCESS);
184 protected Status validateRoles() {
185 if (roles == null || roles.isEmpty()) {
186 return new Status(StatusCode.BADREQUEST, "No role specified");
188 return new Status(StatusCode.SUCCESS);
191 public Status update(String currentPassword, String newPassword, List<String> newRoles) {
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");
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;
206 Status status = proposed.validate();
207 if (!status.isSuccess()) {
211 // Accept the modifications
212 this.user = proposed.user;
213 this.password = proposed.password;
214 this.roles = new ArrayList<String>(proposed.roles);
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());
225 locResponse.setStatus(AuthResultEnum.AUTH_REJECT_LOC);
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()) {
237 buffer.append(iter.next());
240 return buffer.toString();
243 public static String hash(String message) {
244 if (message == null) {
247 UserConfig.oneWayFunction.reset();
248 return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset())));