001/** 002 * Copyright 2014 Tampere University of Technology, Pori Department 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package service.tut.pori.users; 017 018import java.util.EnumSet; 019 020import javax.xml.bind.annotation.XmlAccessType; 021import javax.xml.bind.annotation.XmlAccessorType; 022import javax.xml.bind.annotation.XmlElement; 023import javax.xml.bind.annotation.XmlRootElement; 024 025import org.apache.commons.codec.DecoderException; 026import org.apache.commons.codec.EncoderException; 027import org.apache.commons.codec.net.URLCodec; 028import org.apache.commons.lang3.ArrayUtils; 029import org.apache.commons.lang3.StringUtils; 030import org.apache.commons.lang3.tuple.Pair; 031import org.apache.log4j.Logger; 032import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 033 034import core.tut.pori.context.EventHandler; 035import core.tut.pori.context.ServiceInitializer; 036import core.tut.pori.http.ResponseData; 037import core.tut.pori.users.ExternalAccountConnection; 038import core.tut.pori.users.ExternalAccountConnection.UserServiceType; 039import core.tut.pori.users.UserAuthority; 040import core.tut.pori.users.UserEvent.EventType; 041import core.tut.pori.users.UserIdentity; 042 043/** 044 * User Core methods. 045 * 046 * This class emits events of type {@link service.tut.pori.users.UserServiceEvent} for user account modifications with one of the listed {@link core.tut.pori.users.UserEvent.EventType} : 047 * <ul> 048 * <li>{@link core.tut.pori.users.UserEvent.EventType#USER_CREATED} for newly created user accounts.</li> 049 * <li>{@link core.tut.pori.users.UserEvent.EventType#USER_REMOVED} for removed user accounts.</li> 050 * <li>{@link core.tut.pori.users.UserEvent.EventType#USER_AUTHORIZATION_REVOKED} for removed external account connection. The external connection type will can be retrieved from the service type getter ({@link service.tut.pori.users.UserServiceEvent#getUserServiceType()})</li> 051 * </ul> 052 */ 053public class UserCore { 054 private static final Logger LOGGER = Logger.getLogger(UserCore.class); 055 056 /** 057 * The status of registration process. 058 * 059 */ 060 public enum RegistrationStatus{ 061 /** registeration completed successfully */ 062 OK, 063 /** given username was invalid or reserved */ 064 BAD_USERNAME, 065 /** given password was invalid (too short or contained invalid characters */ 066 BAD_PASSWORD, 067 /** required data was not given */ 068 NULL_DATA, 069 /** Registeration attempt was forbidden. */ 070 FORBIDDEN; 071 072 /** 073 * 074 * @return this status as a string 075 */ 076 public String toStatusString(){ 077 return name(); 078 } 079 } // enum RegistrationStatus 080 081 /** 082 * 083 */ 084 private UserCore(){ 085 // nothing needed 086 } 087 088 /** 089 * @param serviceTypes 090 * @param userId 091 * @throws IllegalArgumentException on bad values 092 */ 093 public static void deleteExternalAccountConnections(EnumSet<UserServiceType> serviceTypes, UserIdentity userId) throws IllegalArgumentException { 094 if(!UserIdentity.isValid(userId)){ 095 throw new IllegalArgumentException("Invalid user identity."); 096 } 097 098 if(serviceTypes == null || serviceTypes.isEmpty()){ 099 LOGGER.warn("Ignored empty service type list."); 100 return; 101 } 102 103 UserDAO userDao = ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class); 104 EventHandler eventHandler = ServiceInitializer.getEventHandler(); 105 for(UserServiceType t : serviceTypes){ 106 if(userDao.deleteExternalAccountConnection(t, userId)){ 107 eventHandler.publishEvent(new UserServiceEvent(EventType.USER_AUTHORIZATION_REVOKED, t, UserCore.class, userId)); 108 }else{ 109 LOGGER.warn("Could not remove requested user service connection "+t.toUserServiceTypeString()+" for user, id: "+userId.getUserId()); 110 } 111 } 112 } 113 114 /** 115 * 116 * @param username 117 * @return user identity for the username or null if not found 118 */ 119 public static UserIdentity getUserIdentity(String username){ 120 return ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).getUser(username); 121 } 122 123 /** 124 * 125 * @param userId 126 * @return user identity for the given id or null if not found 127 */ 128 public static UserIdentity getUserIdentity(Long userId){ 129 return ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).getUser(userId); 130 } 131 132 /** 133 * 134 * @param serviceTypes optional service type filters 135 * @param userId 136 * @return list of connections for the given user or null if none was found 137 */ 138 public static ExternalAccountConnectionList getExternalAccountConnections(EnumSet<UserServiceType> serviceTypes, UserIdentity userId){ 139 return ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).getExternalAccountConnections(serviceTypes, userId); 140 } 141 142 /** 143 * Register a new user, checking for valid system registration password, if one is set in the system properties. 144 * 145 * @param registration 146 * @return status 147 */ 148 public static RegistrationStatus register(Registration registration) { 149 String registerPassword = ServiceInitializer.getPropertyHandler().getSystemProperties(UserServiceProperties.class).getRegisterPassword(); 150 if(!StringUtils.isBlank(registerPassword) && !registerPassword.equals(registration.getRegisterPassword())){ 151 LOGGER.warn("The given registeration password was invalid."); 152 return RegistrationStatus.FORBIDDEN; 153 } 154 155 return createUser(registration); 156 } 157 158 /** 159 * 160 * @param connection 161 * @return UserIdentity with the id value set or null if none is found 162 */ 163 public static UserIdentity getUserId(ExternalAccountConnection connection){ 164 return ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).getUserId(connection); 165 } 166 167 /** 168 * 169 * @param connection 170 * @param userId 171 * @throws IllegalArgumentException 172 */ 173 public static void insertExternalAccountConnection(ExternalAccountConnection connection, UserIdentity userId) throws IllegalArgumentException{ 174 if(!UserIdentity.isValid(userId)){ 175 throw new IllegalArgumentException("Bad userId."); 176 } 177 ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).insertExternalAccountConnection(connection, userId); 178 } 179 180 /** 181 * Crate user based on the registration information. On success this will publish event notification for newly created user. 182 * 183 * @param registration 184 * @return status 185 */ 186 public static RegistrationStatus createUser(Registration registration){ 187 RegistrationStatus status = Registration.isValid(registration); 188 if(status != RegistrationStatus.OK){ 189 LOGGER.debug("Invalid registration."); 190 return status; 191 } 192 193 UserIdentity userId = new UserIdentity(registration.getEncryptedPassword(), null, registration.getUsername()); 194 userId.addAuthority(UserAuthority.AUTHORITY_ROLE_USER); // add with role user 195 if(ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).addUser(userId)){ 196 registration.setRegisteredUserId(userId); 197 ServiceInitializer.getEventHandler().publishEvent(new UserServiceEvent(EventType.USER_CREATED, null, UserCore.class, userId)); 198 return RegistrationStatus.OK; 199 }else{ 200 LOGGER.debug("Failed to add new user: reserved username."); 201 return RegistrationStatus.BAD_USERNAME; 202 } 203 } 204 205 /** 206 * Remove the user from the system. Successful call will publish event notification for removed user. 207 * 208 * @param userId 209 * @throws IllegalArgumentException 210 */ 211 public static void unregister(UserIdentity userId) throws IllegalArgumentException{ 212 if(ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class).removeUser(userId)){ 213 ServiceInitializer.getSessionHandler().removeSessionInformation(userId); // remove all user's sessions 214 ServiceInitializer.getEventHandler().publishEvent(new UserServiceEvent(EventType.USER_REMOVED, null, UserCore.class, userId)); 215 }else{ 216 throw new IllegalArgumentException("Failed to remove user, id: "+userId.getUserId()); 217 } 218 } 219 220 /** 221 * 222 * @param authenticatedUser 223 * @param userIdFilter optional filter for retrieving a list of users, if null, the details of the authenticatedUser will be returned 224 * @return user details for the requested userId or null if not available 225 * @throws IllegalArgumentException 226 */ 227 public static UserIdentityList getUserDetails(UserIdentity authenticatedUser, long[] userIdFilter) throws IllegalArgumentException{ 228 if(!UserIdentity.isValid(authenticatedUser)){ 229 throw new IllegalArgumentException("Bad authenticated user."); 230 } 231 Long authId = authenticatedUser.getUserId(); 232 UserDAO userDAO = ServiceInitializer.getDAOHandler().getSQLDAO(UserDAO.class); 233 authenticatedUser = userDAO.getUser(authId); // populate the details 234 if(!UserIdentity.isValid(authenticatedUser)){ 235 LOGGER.warn("Could not resolve user identity for user, id: "+authId); 236 throw new IllegalArgumentException("Bad authenticated user."); 237 } 238 239 if(ArrayUtils.isEmpty(userIdFilter) || (userIdFilter.length == 1 && userIdFilter[0] == authId)){ // if no filters have been given or the only filter is the authenticated user 240 UserIdentityList list = new UserIdentityList(); 241 list.addUserId(authenticatedUser); 242 return list; 243 } 244 245 if(!authenticatedUser.getAuthorities().contains(UserAuthority.AUTHORITY_ROLE_ADMIN)){ 246 LOGGER.warn("User, id: "+authId+" tried to access user details, but does not have the required role: "+UserAuthority.AUTHORITY_ROLE_ADMIN.getAuthority()); 247 return null; 248 }else{ 249 return userDAO.getUsers(userIdFilter); 250 } 251 } 252 253 /** 254 * Combines nonce and redirectUri together for redirecting things after FB has responded 255 * @param nonce 256 * @param redirectUri 257 * @return the nonce and redirection in a combined, URL encoded form 258 */ 259 public static String urlEncodedCombinedNonce(String nonce, String redirectUri){ 260 String retval = null; 261 try { 262 if(StringUtils.isEmpty(redirectUri)){ 263 retval = new URLCodec().encode(nonce); 264 }else{ 265 retval = new URLCodec().encode(nonce+Definitions.NONCE_SEPARATOR+redirectUri); 266 } 267 } catch (EncoderException ex) { 268 LOGGER.error(ex, ex); 269 } 270 return retval; 271 } 272 273 /** 274 * 275 * @param nonce 276 * @return pair of "nonce","redirectUri" or null if NONCE_SEPARATOR ".-." is not found 277 */ 278 public static Pair<String, String> getNonceAndRedirectUri(String nonce){ 279 Pair<String, String> nonceAndUrl = null; 280 try { 281 String decoded = new URLCodec().decode(nonce); 282 String[] splitted = StringUtils.split(decoded, Definitions.NONCE_SEPARATOR, 2); 283 if(splitted.length > 1){ 284 nonceAndUrl = Pair.of(splitted[0], splitted[1]); 285 }else{ 286 return null; 287 } 288 } catch (DecoderException ex) { 289 LOGGER.error(ex, ex); 290 } 291 return nonceAndUrl; 292 } 293 294 /** 295 * User registration details. 296 * 297 */ 298 @XmlRootElement(name=Definitions.ELEMENT_REGISTRATION) 299 @XmlAccessorType(XmlAccessType.NONE) 300 public static class Registration extends ResponseData{ 301 @XmlElement(name=core.tut.pori.users.Definitions.ELEMENT_USERNAME) 302 private String _username = null; 303 private String _password = null; 304 private String _encryptedPassword = null; 305 private BCryptPasswordEncoder _encoder = null; 306 @XmlElement(name=Definitions.ELEMENT_REGISTER_PASSWORD) 307 private String _registerPassword = null; 308 private UserIdentity _registeredUserId = null; // contains the registered user details after successful registration or null if not available 309 310 /** 311 * @return the username 312 */ 313 public String getUsername() { 314 return _username; 315 } 316 317 /** 318 * @param username the username to set 319 */ 320 public void setUsername(String username) { 321 _username = username; 322 } 323 324 /** 325 * @return the password 326 */ 327 @XmlElement(name=Definitions.ELEMENT_PASSWORD) 328 public String getPassword() { 329 return _password; 330 } 331 332 /** 333 * 334 * @return encrypted password 335 */ 336 public String getEncryptedPassword(){ 337 if(_password == null){ 338 LOGGER.debug("No password."); 339 return null; 340 } 341 if(_encryptedPassword == null){ 342 if(_encoder == null){ 343 _encoder = new BCryptPasswordEncoder(); 344 } 345 _encryptedPassword = _encoder.encode(_password); 346 } 347 return _encryptedPassword; 348 } 349 350 /** 351 * @param password the password to set 352 */ 353 public void setPassword(String password) { 354 _encryptedPassword = null; // may have changed 355 _password = password; 356 } 357 358 /** 359 * for sub-classing, use the static 360 * 361 * @return true if the registration object is valid 362 */ 363 protected RegistrationStatus isValid(){ 364 if(StringUtils.isBlank(_username)){ 365 LOGGER.debug("No username."); 366 return RegistrationStatus.BAD_USERNAME; 367 }else if(StringUtils.isBlank(_password)){ 368 LOGGER.debug("No password."); 369 return RegistrationStatus.BAD_PASSWORD; 370 }else{ 371 return RegistrationStatus.OK; 372 } 373 } 374 375 /** 376 * 377 * @param registration can be null 378 * @return true if the passed registration object is valid 379 */ 380 public static RegistrationStatus isValid(Registration registration){ 381 if(registration == null){ 382 return RegistrationStatus.NULL_DATA; 383 }else{ 384 return registration.isValid(); 385 } 386 } 387 388 /** 389 * Returns the registered user details after successful registration 390 * 391 * @return the registeredUserId 392 */ 393 public UserIdentity getRegisteredUserId() { 394 return _registeredUserId; 395 } 396 397 /** 398 * @param registeredUserId the registeredUserId to set 399 */ 400 protected void setRegisteredUserId(UserIdentity registeredUserId) { 401 _registeredUserId = registeredUserId; 402 } 403 404 /** 405 * @return the registerPassword 406 */ 407 public String getRegisterPassword() { 408 return _registerPassword; 409 } 410 411 /** 412 * @param registerPassword the registerPassword to set 413 */ 414 public void setRegisterPassword(String registerPassword) { 415 _registerPassword = registerPassword; 416 } 417 } // class Registration 418}