1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.guacamole.auth.jdbc.user; 21 22 import com.google.inject.Inject; 23 import com.google.inject.Provider; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Collection; 27 import java.util.Collections; 28 import java.util.List; 29 import javax.servlet.http.HttpServletRequest; 30 import org.apache.guacamole.net.auth.Credentials; 31 import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper; 32 import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectService; 33 import org.apache.guacamole.GuacamoleClientException; 34 import org.apache.guacamole.GuacamoleException; 35 import org.apache.guacamole.GuacamoleUnsupportedException; 36 import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel; 37 import org.apache.guacamole.auth.jdbc.base.ActivityRecordSearchTerm; 38 import org.apache.guacamole.auth.jdbc.base.ActivityRecordSortPredicate; 39 import org.apache.guacamole.auth.jdbc.base.EntityMapper; 40 import org.apache.guacamole.auth.jdbc.base.ModeledActivityRecord; 41 import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper; 42 import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionModel; 43 import org.apache.guacamole.auth.jdbc.permission.UserPermissionMapper; 44 import org.apache.guacamole.auth.jdbc.security.PasswordEncryptionService; 45 import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService; 46 import org.apache.guacamole.form.Field; 47 import org.apache.guacamole.form.PasswordField; 48 import org.apache.guacamole.language.TranslatableGuacamoleClientException; 49 import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException; 50 import org.apache.guacamole.net.auth.ActivityRecord; 51 import org.apache.guacamole.net.auth.AuthenticatedUser; 52 import org.apache.guacamole.net.auth.AuthenticationProvider; 53 import org.apache.guacamole.net.auth.User; 54 import org.apache.guacamole.net.auth.credentials.CredentialsInfo; 55 import org.apache.guacamole.net.auth.permission.ObjectPermission; 56 import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; 57 import org.apache.guacamole.net.auth.permission.SystemPermission; 58 import org.apache.guacamole.net.auth.permission.SystemPermissionSet; 59 import org.slf4j.Logger; 60 import org.slf4j.LoggerFactory; 61 62 /** 63 * Service which provides convenience methods for creating, retrieving, and 64 * manipulating users. 65 */ 66 public class UserService extends ModeledDirectoryObjectService<ModeledUser, User, UserModel> { 67 68 /** 69 * Logger for this class. 70 */ 71 private static final Logger logger = LoggerFactory.getLogger(UserService.class); 72 73 /** 74 * All user permissions which are implicitly granted to the new user upon 75 * creation. 76 */ 77 private static final ObjectPermission.Type[] IMPLICIT_USER_PERMISSIONS = { 78 ObjectPermission.Type.READ 79 }; 80 81 /** 82 * The name of the HTTP password parameter to expect if the user is 83 * changing their expired password upon login. 84 */ 85 private static final String NEW_PASSWORD_PARAMETER = "new-password"; 86 87 /** 88 * The password field to provide the user when their password is expired 89 * and must be changed. 90 */ 91 private static final Field NEW_PASSWORD = new PasswordField(NEW_PASSWORD_PARAMETER); 92 93 /** 94 * The name of the HTTP password confirmation parameter to expect if the 95 * user is changing their expired password upon login. 96 */ 97 private static final String CONFIRM_NEW_PASSWORD_PARAMETER = "confirm-new-password"; 98 99 /** 100 * The password confirmation field to provide the user when their password 101 * is expired and must be changed. 102 */ 103 private static final Field CONFIRM_NEW_PASSWORD = new PasswordField(CONFIRM_NEW_PASSWORD_PARAMETER); 104 105 /** 106 * Information describing the expected credentials if a user's password is 107 * expired. If a user's password is expired, it must be changed during the 108 * login process. 109 */ 110 private static final CredentialsInfo EXPIRED_PASSWORD = new CredentialsInfo(Arrays.asList( 111 CredentialsInfo.USERNAME, 112 CredentialsInfo.PASSWORD, 113 NEW_PASSWORD, 114 CONFIRM_NEW_PASSWORD 115 )); 116 117 /** 118 * Mapper for creating/deleting entities. 119 */ 120 @Inject 121 private EntityMapper entityMapper; 122 123 /** 124 * Mapper for accessing users. 125 */ 126 @Inject 127 private UserMapper userMapper; 128 129 /** 130 * Mapper for manipulating user permissions. 131 */ 132 @Inject 133 private UserPermissionMapper userPermissionMapper; 134 135 /** 136 * Mapper for accessing user login history. 137 */ 138 @Inject 139 private UserRecordMapper userRecordMapper; 140 141 /** 142 * Provider for creating users. 143 */ 144 @Inject 145 private Provider<ModeledUser> userProvider; 146 147 /** 148 * Service for hashing passwords. 149 */ 150 @Inject 151 private PasswordEncryptionService encryptionService; 152 153 /** 154 * Service for enforcing password complexity policies. 155 */ 156 @Inject 157 private PasswordPolicyService passwordPolicyService; 158 159 @Override getObjectMapper()160 protected ModeledDirectoryObjectMapper<UserModel> getObjectMapper() { 161 return userMapper; 162 } 163 164 @Override getPermissionMapper()165 protected ObjectPermissionMapper getPermissionMapper() { 166 return userPermissionMapper; 167 } 168 169 @Override getObjectInstance(ModeledAuthenticatedUser currentUser, UserModel model)170 protected ModeledUser getObjectInstance(ModeledAuthenticatedUser currentUser, 171 UserModel model) throws GuacamoleException { 172 173 boolean exposeRestrictedAttributes; 174 175 // Expose restricted attributes if the user does not yet exist 176 if (model.getObjectID() == null) 177 exposeRestrictedAttributes = true; 178 179 // Otherwise, if the user permissions are available, expose restricted 180 // attributes only if the user has ADMINISTER permission 181 else if (currentUser != null) 182 exposeRestrictedAttributes = hasObjectPermission(currentUser, 183 model.getIdentifier(), ObjectPermission.Type.ADMINISTER); 184 185 // If user permissions are not available, do not expose anything 186 else 187 exposeRestrictedAttributes = false; 188 189 // Produce ModeledUser exposing only those attributes for which the 190 // current user has permission 191 ModeledUser user = userProvider.get(); 192 user.init(currentUser, model, exposeRestrictedAttributes); 193 return user; 194 195 } 196 197 @Override getModelInstance(ModeledAuthenticatedUser currentUser, final User object)198 protected UserModel getModelInstance(ModeledAuthenticatedUser currentUser, 199 final User object) throws GuacamoleException { 200 201 // Create new ModeledUser backed by blank model 202 UserModel model = new UserModel(); 203 ModeledUser user = getObjectInstance(currentUser, model); 204 205 // Set model contents through ModeledUser, copying the provided user 206 user.setIdentifier(object.getIdentifier()); 207 user.setPassword(object.getPassword()); 208 user.setAttributes(object.getAttributes()); 209 210 return model; 211 212 } 213 214 @Override hasCreatePermission(ModeledAuthenticatedUser user)215 protected boolean hasCreatePermission(ModeledAuthenticatedUser user) 216 throws GuacamoleException { 217 218 // Return whether user has explicit user creation permission 219 SystemPermissionSet permissionSet = user.getUser().getEffectivePermissions().getSystemPermissions(); 220 return permissionSet.hasPermission(SystemPermission.Type.CREATE_USER); 221 222 } 223 224 @Override getEffectivePermissionSet(ModeledAuthenticatedUser user)225 protected ObjectPermissionSet getEffectivePermissionSet(ModeledAuthenticatedUser user) 226 throws GuacamoleException { 227 228 // Return permissions related to users 229 return user.getUser().getEffectivePermissions().getUserPermissions(); 230 231 } 232 233 @Override beforeCreate(ModeledAuthenticatedUser user, User object, UserModel model)234 protected void beforeCreate(ModeledAuthenticatedUser user, User object, 235 UserModel model) throws GuacamoleException { 236 237 super.beforeCreate(user, object, model); 238 239 // Username must not be blank 240 if (model.getIdentifier() == null || model.getIdentifier().trim().isEmpty()) 241 throw new GuacamoleClientException("The username must not be blank."); 242 243 // Do not create duplicate users 244 Collection<UserModel> existing = userMapper.select(Collections.singleton(model.getIdentifier())); 245 if (!existing.isEmpty()) 246 throw new GuacamoleClientException("User \"" + model.getIdentifier() + "\" already exists."); 247 248 // Verify new password does not violate defined policies (if specified) 249 if (object.getPassword() != null) 250 passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword()); 251 252 // Create base entity object, implicitly populating underlying entity ID 253 entityMapper.insert(model); 254 255 } 256 257 @Override beforeUpdate(ModeledAuthenticatedUser user, ModeledUser object, UserModel model)258 protected void beforeUpdate(ModeledAuthenticatedUser user, 259 ModeledUser object, UserModel model) throws GuacamoleException { 260 261 super.beforeUpdate(user, object, model); 262 263 // Refuse to update if the user is a skeleton and does not actually 264 // exist in the database (this will happen if the user is authenticated 265 // via a non-database authentication provider) 266 if (object.isSkeleton()) { 267 logger.info("Data cannot be stored for user \"{}\" as they do not " 268 + "have an account within the database. If this is " 269 + "unexpected, consider allowing automatic creation of " 270 + "user accounts.", object.getIdentifier()); 271 throw new GuacamoleUnsupportedException("User does not exist " 272 + "within the database and cannot be updated."); 273 } 274 275 // Username must not be blank 276 if (model.getIdentifier() == null || model.getIdentifier().trim().isEmpty()) 277 throw new GuacamoleClientException("The username must not be blank."); 278 279 // Check whether such a user is already present 280 UserModel existing = userMapper.selectOne(model.getIdentifier()); 281 if (existing != null) { 282 283 // Do not rename to existing user 284 if (!existing.getObjectID().equals(model.getObjectID())) 285 throw new GuacamoleClientException("User \"" + model.getIdentifier() + "\" already exists."); 286 287 } 288 289 // Verify new password does not violate defined policies (if specified) 290 if (object.getPassword() != null) { 291 292 // Enforce password age only for non-privileged users 293 if (!user.isPrivileged()) 294 passwordPolicyService.verifyPasswordAge(object); 295 296 // Always verify password complexity 297 passwordPolicyService.verifyPassword(object.getIdentifier(), object.getPassword()); 298 299 // Store previous password in history 300 passwordPolicyService.recordPassword(object); 301 302 } 303 304 } 305 306 @Override 307 protected Collection<ObjectPermissionModel> getImplicitPermissions(ModeledAuthenticatedUser user, UserModel model)308 getImplicitPermissions(ModeledAuthenticatedUser user, UserModel model) { 309 310 // Get original set of implicit permissions and make a copy 311 Collection<ObjectPermissionModel> implicitPermissions = 312 new ArrayList<>(super.getImplicitPermissions(user, model)); 313 314 // Grant implicit permissions to the new user 315 for (ObjectPermission.Type permissionType : IMPLICIT_USER_PERMISSIONS) { 316 317 ObjectPermissionModel permissionModel = new ObjectPermissionModel(); 318 permissionModel.setEntityID(model.getEntityID()); 319 permissionModel.setType(permissionType); 320 permissionModel.setObjectIdentifier(model.getIdentifier()); 321 322 // Add new permission to implicit permission set 323 implicitPermissions.add(permissionModel); 324 325 } 326 327 return Collections.unmodifiableCollection(implicitPermissions); 328 } 329 330 @Override beforeDelete(ModeledAuthenticatedUser user, String identifier)331 protected void beforeDelete(ModeledAuthenticatedUser user, String identifier) throws GuacamoleException { 332 333 super.beforeDelete(user, identifier); 334 335 // Do not allow users to delete themselves 336 if (identifier.equals(user.getUser().getIdentifier())) 337 throw new GuacamoleUnsupportedException("Deleting your own user is not allowed."); 338 339 } 340 341 @Override isValidIdentifier(String identifier)342 protected boolean isValidIdentifier(String identifier) { 343 344 // All strings are valid user identifiers 345 return true; 346 347 } 348 349 /** 350 * Retrieves the user corresponding to the given credentials from the 351 * database. Note that this function will not enforce any additional 352 * account restrictions, including explicitly disabled accounts, 353 * scheduling, and password expiration. It is the responsibility of the 354 * caller to enforce such restrictions, if desired. 355 * 356 * @param authenticationProvider 357 * The AuthenticationProvider on behalf of which the user is being 358 * retrieved. 359 * 360 * @param credentials 361 * The credentials to use when locating the user. 362 * 363 * @return 364 * An AuthenticatedUser containing the existing ModeledUser object if 365 * the credentials given are valid, null otherwise. 366 * 367 * @throws GuacamoleException 368 * If the provided credentials to not conform to expectations. 369 */ retrieveAuthenticatedUser(AuthenticationProvider authenticationProvider, Credentials credentials)370 public ModeledAuthenticatedUser retrieveAuthenticatedUser(AuthenticationProvider authenticationProvider, 371 Credentials credentials) throws GuacamoleException { 372 373 // Get username and password 374 String username = credentials.getUsername(); 375 String password = credentials.getPassword(); 376 377 // Retrieve corresponding user model, if such a user exists 378 UserModel userModel = userMapper.selectOne(username); 379 if (userModel == null) 380 return null; 381 382 // Verify provided password is correct 383 byte[] hash = encryptionService.createPasswordHash(password, userModel.getPasswordSalt()); 384 if (!Arrays.equals(hash, userModel.getPasswordHash())) 385 return null; 386 387 // Create corresponding user object, set up cyclic reference 388 ModeledUser user = getObjectInstance(null, userModel); 389 user.setCurrentUser(new ModeledAuthenticatedUser(authenticationProvider, user, credentials)); 390 391 // Return now-authenticated user 392 return user.getCurrentUser(); 393 394 } 395 396 /** 397 * Retrieves the user corresponding to the given AuthenticatedUser from the 398 * database. 399 * 400 * @param authenticationProvider 401 * The AuthenticationProvider on behalf of which the user is being 402 * retrieved. 403 * 404 * @param authenticatedUser 405 * The AuthenticatedUser to retrieve the corresponding ModeledUser of. 406 * 407 * @return 408 * The ModeledUser which corresponds to the given AuthenticatedUser, or 409 * null if no such user exists. 410 * 411 * @throws GuacamoleException 412 * If a ModeledUser object for the user corresponding to the given 413 * AuthenticatedUser cannot be created. 414 */ retrieveUser(AuthenticationProvider authenticationProvider, AuthenticatedUser authenticatedUser)415 public ModeledUser retrieveUser(AuthenticationProvider authenticationProvider, 416 AuthenticatedUser authenticatedUser) throws GuacamoleException { 417 418 // If we already queried this user, return that rather than querying again 419 if (authenticatedUser instanceof ModeledAuthenticatedUser) 420 return ((ModeledAuthenticatedUser) authenticatedUser).getUser(); 421 422 // Retrieve corresponding user model, if such a user exists 423 UserModel userModel = userMapper.selectOne(authenticatedUser.getIdentifier()); 424 if (userModel == null) 425 return null; 426 427 // Create corresponding user object, set up cyclic reference 428 ModeledUser user = getObjectInstance(null, userModel); 429 user.setCurrentUser(new ModeledAuthenticatedUser(authenticatedUser, 430 authenticationProvider, user)); 431 432 // Return already-authenticated user 433 return user; 434 435 } 436 437 /** 438 * Generates an empty (skeleton) user corresponding to the given 439 * AuthenticatedUser. The user will not be stored in the database, and 440 * will only be available in-memory during the time the session is 441 * active. 442 * 443 * @param authenticationProvider 444 * The AuthenticationProvider on behalf of which the user is being 445 * retrieved. 446 * 447 * @param authenticatedUser 448 * The AuthenticatedUser to generate the skeleton account for. 449 * 450 * @return 451 * The empty ModeledUser which corresponds to the given 452 * AuthenticatedUser. 453 * 454 * @throws GuacamoleException 455 * If a ModeledUser object for the user corresponding to the given 456 * AuthenticatedUser cannot be created. 457 */ retrieveSkeletonUser(AuthenticationProvider authenticationProvider, AuthenticatedUser authenticatedUser)458 public ModeledUser retrieveSkeletonUser(AuthenticationProvider authenticationProvider, 459 AuthenticatedUser authenticatedUser) throws GuacamoleException { 460 461 // Set up an empty user model 462 ModeledUser user = getObjectInstance(null, 463 new UserModel(authenticatedUser.getIdentifier())); 464 465 // Create user object, and configure cyclic reference 466 user.setCurrentUser(new ModeledAuthenticatedUser(authenticatedUser, 467 authenticationProvider, user)); 468 469 // Return the new user. 470 return user; 471 472 } 473 474 /** 475 * Resets the password of the given user to the new password specified via 476 * the "new-password" and "confirm-new-password" parameters from the 477 * provided credentials. If these parameters are missing or invalid, 478 * additional credentials will be requested. 479 * 480 * @param user 481 * The user whose password should be reset. 482 * 483 * @param credentials 484 * The credentials from which the parameters required for password 485 * reset should be retrieved. 486 * 487 * @throws GuacamoleException 488 * If the password reset parameters within the given credentials are 489 * invalid or missing. 490 */ resetExpiredPassword(ModeledUser user, Credentials credentials)491 public void resetExpiredPassword(ModeledUser user, Credentials credentials) 492 throws GuacamoleException { 493 494 UserModel userModel = user.getModel(); 495 496 // Get username 497 String username = user.getIdentifier(); 498 499 // Pull new password from HTTP request 500 HttpServletRequest request = credentials.getRequest(); 501 String newPassword = request.getParameter(NEW_PASSWORD_PARAMETER); 502 String confirmNewPassword = request.getParameter(CONFIRM_NEW_PASSWORD_PARAMETER); 503 504 // Require new password if account is expired 505 if (newPassword == null || confirmNewPassword == null) { 506 logger.info("The password of user \"{}\" has expired and must be reset.", username); 507 throw new TranslatableGuacamoleInsufficientCredentialsException("Password has expired", 508 "LOGIN.INFO_PASSWORD_EXPIRED", EXPIRED_PASSWORD); 509 } 510 511 // New password must be different from old password 512 if (newPassword.equals(credentials.getPassword())) 513 throw new TranslatableGuacamoleClientException("New passwords may " 514 + "not be identical to the current password if password " 515 + "reset is required.", "LOGIN.ERROR_PASSWORD_SAME"); 516 517 // New password must not be blank 518 if (newPassword.isEmpty()) 519 throw new TranslatableGuacamoleClientException("Passwords may not " 520 + "be blank.", "LOGIN.ERROR_PASSWORD_BLANK"); 521 522 // Confirm that the password was entered correctly twice 523 if (!newPassword.equals(confirmNewPassword)) 524 throw new TranslatableGuacamoleClientException("New password does " 525 + "not match.", "LOGIN.ERROR_PASSWORD_MISMATCH"); 526 527 // Verify new password does not violate defined policies 528 passwordPolicyService.verifyPassword(username, newPassword); 529 530 // Change password and reset expiration flag 531 userModel.setExpired(false); 532 user.setPassword(newPassword); 533 userMapper.update(userModel); 534 logger.info("Expired password of user \"{}\" has been reset.", username); 535 536 } 537 538 /** 539 * Returns a ActivityRecord object which is backed by the given model. 540 * 541 * @param model 542 * The model object to use to back the returned connection record 543 * object. 544 * 545 * @return 546 * A connection record object which is backed by the given model. 547 */ getObjectInstance(ActivityRecordModel model)548 protected ActivityRecord getObjectInstance(ActivityRecordModel model) { 549 return new ModeledActivityRecord(model); 550 } 551 552 /** 553 * Returns a list of ActivityRecord objects which are backed by the 554 * models in the given list. 555 * 556 * @param models 557 * The model objects to use to back the activity record objects 558 * within the returned list. 559 * 560 * @return 561 * A list of activity record objects which are backed by the models 562 * in the given list. 563 */ getObjectInstances(List<ActivityRecordModel> models)564 protected List<ActivityRecord> getObjectInstances(List<ActivityRecordModel> models) { 565 566 // Create new list of records by manually converting each model 567 List<ActivityRecord> objects = new ArrayList<ActivityRecord>(models.size()); 568 for (ActivityRecordModel model : models) 569 objects.add(getObjectInstance(model)); 570 571 return objects; 572 573 } 574 575 /** 576 * Retrieves the login history of the given user, including any active 577 * sessions. 578 * 579 * @param authenticatedUser 580 * The user retrieving the login history. 581 * 582 * @param user 583 * The user whose history is being retrieved. 584 * 585 * @return 586 * The login history of the given user, including any active sessions. 587 * 588 * @throws GuacamoleException 589 * If permission to read the login history is denied. 590 */ retrieveHistory(ModeledAuthenticatedUser authenticatedUser, ModeledUser user)591 public List<ActivityRecord> retrieveHistory(ModeledAuthenticatedUser authenticatedUser, 592 ModeledUser user) throws GuacamoleException { 593 594 String username = user.getIdentifier(); 595 596 return retrieveHistory(username, authenticatedUser, Collections.emptyList(), 597 Collections.emptyList(), Integer.MAX_VALUE); 598 599 } 600 601 /** 602 * Retrieves user login history records matching the given criteria. 603 * Retrieves up to <code>limit</code> user history records matching the 604 * given terms and sorted by the given predicates. Only history records 605 * associated with data that the given user can read are returned. 606 * 607 * @param username 608 * The optional username to which history records should be limited, or 609 * null if all readable records should be retrieved. 610 * 611 * @param user 612 * The user retrieving the login history. 613 * 614 * @param requiredContents 615 * The search terms that must be contained somewhere within each of the 616 * returned records. 617 * 618 * @param sortPredicates 619 * A list of predicates to sort the returned records by, in order of 620 * priority. 621 * 622 * @param limit 623 * The maximum number of records that should be returned. 624 * 625 * @return 626 * The login history of the given user, including any active sessions. 627 * 628 * @throws GuacamoleException 629 * If permission to read the user login history is denied. 630 */ retrieveHistory(String username, ModeledAuthenticatedUser user, Collection<ActivityRecordSearchTerm> requiredContents, List<ActivityRecordSortPredicate> sortPredicates, int limit)631 public List<ActivityRecord> retrieveHistory(String username, 632 ModeledAuthenticatedUser user, 633 Collection<ActivityRecordSearchTerm> requiredContents, 634 List<ActivityRecordSortPredicate> sortPredicates, int limit) 635 throws GuacamoleException { 636 637 List<ActivityRecordModel> searchResults; 638 639 // Bypass permission checks if the user is privileged 640 if (user.isPrivileged()) 641 searchResults = userRecordMapper.search(username, requiredContents, 642 sortPredicates, limit); 643 644 // Otherwise only return explicitly readable history records 645 else 646 searchResults = userRecordMapper.searchReadable(username, 647 user.getUser().getModel(), 648 requiredContents, sortPredicates, limit, user.getEffectiveUserGroups()); 649 650 return getObjectInstances(searchResults); 651 652 } 653 654 /** 655 * Retrieves user login history records matching the given criteria. 656 * Retrieves up to <code>limit</code> user history records matching the 657 * given terms and sorted by the given predicates. Only history records 658 * associated with data that the given user can read are returned. 659 * 660 * @param user 661 * The user retrieving the login history. 662 * 663 * @param requiredContents 664 * The search terms that must be contained somewhere within each of the 665 * returned records. 666 * 667 * @param sortPredicates 668 * A list of predicates to sort the returned records by, in order of 669 * priority. 670 * 671 * @param limit 672 * The maximum number of records that should be returned. 673 * 674 * @return 675 * The login history of the given user, including any active sessions. 676 * 677 * @throws GuacamoleException 678 * If permission to read the user login history is denied. 679 */ retrieveHistory(ModeledAuthenticatedUser user, Collection<ActivityRecordSearchTerm> requiredContents, List<ActivityRecordSortPredicate> sortPredicates, int limit)680 public List<ActivityRecord> retrieveHistory(ModeledAuthenticatedUser user, 681 Collection<ActivityRecordSearchTerm> requiredContents, 682 List<ActivityRecordSortPredicate> sortPredicates, int limit) 683 throws GuacamoleException { 684 685 return retrieveHistory(null, user, requiredContents, sortPredicates, limit); 686 687 } 688 689 } 690