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