001/* 002 * Copyright 2012 Anyware Services 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 org.ametys.core.user; 017 018import java.io.Closeable; 019import java.io.IOException; 020import java.io.InputStream; 021import java.time.ZonedDateTime; 022import java.util.List; 023import java.util.TimeZone; 024import java.util.stream.Collectors; 025import java.util.stream.Stream; 026 027import org.apache.commons.lang3.StringUtils; 028import org.slf4j.Logger; 029 030import org.ametys.core.user.dataprovider.UserDataProvider; 031import org.ametys.core.user.directory.StoredUser; 032import org.ametys.core.user.directory.UserDirectory; 033import org.ametys.core.user.status.UserStatusManager; 034import org.ametys.core.util.SizeUtils.ExcludeFromSizeCalculation; 035 036/** 037 * Implementation of the principal abstraction to represent an user with a login and a {@link StoredUser} 038 * @param <T> The type of the factory 039 */ 040public class User <T extends UserFactory> implements java.security.Principal 041{ 042 /** The id of the last name data. This data is of type {@link String} */ 043 public static final String LAST_NAME_DATA_ID = "lastname"; 044 /** The id of the first name data. This data is of type {@link String} */ 045 public static final String FIRST_NAME_DATA_ID = "firstname"; 046 /** The id of the email data. This data is of type {@link String} */ 047 public static final String EMAIL_DATA_ID = "email"; 048 /** The id of the image data. This data is of type {@link UserImage} */ 049 public static final String IMAGE_DATA_ID = "image"; 050 /** The id of the language data. This data is of type String */ 051 public static final String LANGUAGE_DATA_ID = "language"; 052 /** The id of the timezone data. This data is of type {@link TimeZone} or is a TimeZoneId of type {@link String} */ 053 public static final String TIMEZONE_DATA_ID = "timezone"; 054 /** The id of the creation date data. This data is of type {@link ZonedDateTime} */ 055 public static final String CREATION_DATE_DATA_ID = "creation-date"; 056 /** The id of the creation origin data. This data is of type {@link UserCreationOrigin} */ 057 public static final String CREATION_ORIGIN_DATA_ID = "creation-origin"; 058 059 /** 060 * The identity of this principal. 061 */ 062 protected UserIdentity _identity; 063 /** 064 * The user directory this user belongs to. 065 */ 066 @ExcludeFromSizeCalculation 067 protected UserDirectory _userDirectory; 068 069 /** The factory */ 070 @ExcludeFromSizeCalculation 071 protected T _factory; 072 073 @ExcludeFromSizeCalculation 074 private Logger _logger; 075 076 private StoredUser _storedUser; 077 078 /** 079 * Enumeration for the user creation origin 080 * 081 */ 082 public enum UserCreationOrigin 083 { 084 /** User created by system */ 085 SYSTEM, 086 /** User created by an administrator */ 087 ADMIN, 088 /** User created by user signup */ 089 USER_SIGNUP, 090 /** When user creation is unknown or not available */ 091 NOT_AVAILABLE 092 } 093 094 /** 095 * Basic structure holding necessary data representing an user profile image 096 */ 097 public static class UserImage implements Closeable 098 { 099 private final InputStream _inputstream; 100 private final String _filename; 101 private final Long _length; 102 private final Long _lastModified; 103 private final String _type; 104 105 /** 106 * Constructor 107 * @param inputstream The image input stream 108 * @param filename The file name or null if unknown 109 * @param length The file length if known 110 * @param lastModified The last modified date 111 * @param type The type name of the source of the image to compute a cache key 112 */ 113 public UserImage(InputStream inputstream, String filename, Long length, Long lastModified, String type) 114 { 115 _inputstream = inputstream; 116 _filename = filename; 117 _length = length; 118 _lastModified = lastModified; 119 _type = type; 120 } 121 122 /** 123 * Retrieve the type name of the source of the image to compute a cache key 124 * @return the type 125 */ 126 public String getType() 127 { 128 return _type; 129 } 130 131 /** 132 * Retrieves the input stream 133 * @return the input stream 134 */ 135 public InputStream getInputstream() 136 { 137 return _inputstream; 138 } 139 140 /** 141 * Retrieves the filename 142 * @return the filename or null if not defined 143 */ 144 public String getFilename() 145 { 146 return _filename; 147 } 148 149 /** 150 * Retrieves the length 151 * @return the length or null or -1 if unknown 152 */ 153 public Long getLength() 154 { 155 return _length; 156 } 157 158 /** 159 * Retrieve the last modified date. 160 * @return The last modified date. Can be null if unknown. 161 */ 162 public Long getLastModified() 163 { 164 return _lastModified; 165 } 166 167 /** 168 * Close input stream 169 * @implNote Close the input stream of the image. 170 */ 171 public void close() throws IOException 172 { 173 _inputstream.close(); 174 } 175 } 176 177 /** 178 * Construct a new UserPrincipal, associated with a last name, a first name, 179 * an email and identified by a login. 180 * @param storedUser The storedUser. Cannot be null 181 * @param userDirectory The user directory the user belongs to. Cannot be null. 182 * @param factory The factory 183 */ 184 User(StoredUser storedUser, UserDirectory userDirectory, T factory, Logger logger) 185 { 186 _identity = new UserIdentity(storedUser.getIdentifier(), userDirectory.getPopulationId()); 187 _storedUser = storedUser; 188 _userDirectory = userDirectory; 189 _factory = factory; 190 _logger = logger; 191 } 192 193 /** 194 * The identity of the user. 195 * 196 * @return The identity. 197 */ 198 public UserIdentity getIdentity() 199 { 200 return _identity; 201 } 202 203 /** 204 * Get the StoredUser of the User 205 * @return The {@link StoredUser} 206 */ 207 public StoredUser getStoredUser() 208 { 209 return _storedUser; 210 } 211 212 @Override 213 public String getName() 214 { 215 return UserIdentity.userIdentityToString(_identity); 216 } 217 218 /** 219 * The user directory this user belongs to. 220 * @return The user directory 221 */ 222 public UserDirectory getUserDirectory() 223 { 224 return _userDirectory; 225 } 226 227 /** 228 * The last name of the user 229 * @return The last name. 230 */ 231 public String getLastName() 232 { 233 return (String) getValue(LAST_NAME_DATA_ID); 234 } 235 236 /** 237 * The first name of the user 238 * @return The first name. 239 */ 240 public String getFirstName() 241 { 242 return (String) getValue(FIRST_NAME_DATA_ID); 243 } 244 245 /** 246 * The email of the user represented by this Principal. 247 * 248 * @return The email. 249 */ 250 public String getEmail() 251 { 252 return (String) getValue(EMAIL_DATA_ID); 253 } 254 255 /** 256 * The fullname of this user. 257 * @return The full name 258 */ 259 public String getFullName() 260 { 261 return _getFullName(true); 262 } 263 264 /** 265 * The fullname to use to display if sort is needed. 266 * Ensure the sort will be on 267 * @return The sortable name 268 */ 269 public String getSortableName() 270 { 271 return _getFullName(false); 272 } 273 274 /** 275 * Get user's avatar from the {@link UserDataProvider} with the biggest priority 276 * @param size The size 277 * @param maxSize The maxSize 278 * @return A {@link UserImage} the user's avatar or default image if an error occurs 279 */ 280 public UserImage getImage(int size, int maxSize) 281 { 282 UserImageAccessor imageAccessor = (UserImageAccessor) getValue(IMAGE_DATA_ID); 283 284 try 285 { 286 return imageAccessor != null ? imageAccessor.getImage(size, maxSize) : null; 287 } 288 catch (Exception e) 289 { 290 _logger.error("An error occurred while retrieving the profile image for user '" + getIdentity() + "'. Returning default image.", e); 291 return _factory.getDefaultUserImageHelper().getDefaultImage(); 292 } 293 } 294 295 /** 296 * Get user's language from the {@link UserDataProvider} with the biggest priority 297 * @return The user's language 298 */ 299 public String getLanguage() 300 { 301 return (String) getValue(LANGUAGE_DATA_ID); 302 } 303 304 /** 305 * Get user's TimeZone from the {@link UserDataProvider} with the biggest priority 306 * @return The user's timezone 307 */ 308 public TimeZone getTimeZone() 309 { 310 Object timezoneFound = getValue(TIMEZONE_DATA_ID); 311 if (timezoneFound instanceof TimeZone timeZone) 312 { 313 return timeZone; 314 } 315 else if (timezoneFound instanceof String timeZoneId && List.of(TimeZone.getAvailableIDs()).contains(timeZoneId)) 316 { 317 return TimeZone.getTimeZone(timeZoneId); 318 } 319 320 return null; 321 } 322 323 /** 324 * Get the last connection date of the user 325 * @implNote in safe mode, the information is not available and this method always return null 326 * @return the last connection date or null if the user never logged in 327 */ 328 public ZonedDateTime getLastConnectionDate() 329 { 330 UserStatusManager userStatusManager = _factory.getUserStatusManager(); 331 if (userStatusManager != null) 332 { 333 return userStatusManager.getLastConnectionDate(this.getIdentity()).orElse(null); 334 } 335 return null; 336 } 337 338 /** 339 * Get the user's creation date 340 * @return the creation date 341 */ 342 public ZonedDateTime getCreationDate() 343 { 344 return (ZonedDateTime) getValue(CREATION_DATE_DATA_ID); 345 } 346 347 /** 348 * Get the user's creation origin 349 * @return the creation origin 350 */ 351 public UserCreationOrigin getCreationOrigin() 352 { 353 return (UserCreationOrigin) getValue(CREATION_ORIGIN_DATA_ID); 354 } 355 356 /** 357 * Get a user's data from the {@link UserDataProvider} with the biggest priority 358 * @param dataId The data id 359 * @return A user's data 360 */ 361 public Object getValue(String dataId) 362 { 363 return _factory.getUserDataProviderEP().getValue(this, dataId); 364 } 365 366 /** 367 * Get the {@link UserDataProvider} with the biggest priority and a value not null for the data requested 368 * @param dataId The data id 369 * @return A {@link UserDataProvider} the user data provider which is going to provide the data requested 370 */ 371 public UserDataProvider getProviderFor(String dataId) 372 { 373 return _factory.getUserDataProviderEP().getProviderFor(this, dataId); 374 } 375 376 /** 377 * The full name of the user represented by this Principal. 378 * @param firstnameThenLastname Define the name order in the full name. Set to <code>true</code> to retrieve the fullname with firstname first and lastname second. Set to <code>false</code> to retrieve the fullname with lastname first and firstname second. 379 * @return The full name. 380 */ 381 protected String _getFullName(boolean firstnameThenLastname) 382 { 383 Stream<String> stream = firstnameThenLastname 384 ? Stream.of(getFirstName(), getLastName()) 385 : Stream.of(getLastName(), getFirstName()); 386 387 String sortableName = stream.filter(StringUtils::isNotEmpty) 388 .collect(Collectors.joining(" ")); 389 390 return StringUtils.defaultIfEmpty(sortableName, _identity.getLogin()); 391 } 392 393 /** 394 * Return a String representation of this object, which exposes only 395 * information that should be public. 396 * 397 * @return A string representing the user. 398 */ 399 @Override 400 public String toString() 401 { 402 StringBuilder sb = new StringBuilder("Principal["); 403 sb.append(_userDirectory.getPopulationId()); 404 sb.append(", "); 405 sb.append(_storedUser.toString()); 406 sb.append("]"); 407 return sb.toString(); 408 } 409 410 /** 411 * Test if two principal are equals. 412 * @return true if the given Object represents the same Principal. 413 */ 414 @Override 415 public boolean equals(Object another) 416 { 417 if (another == null || !(another instanceof User)) 418 { 419 return false; 420 } 421 422 User otherUser = (User) another; 423 424 return _identity != null && _identity.equals(otherUser.getIdentity()); 425 } 426 427 @Override 428 public int hashCode() 429 { 430 return _identity.hashCode(); 431 } 432}