http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java index fb06e6d..9227366 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java @@ -65,7 +65,7 @@ import org.apache.ambari.server.orm.entities.StageEntity; import org.apache.ambari.server.security.ClientSecurityType; import org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosAuthenticationProperties; import org.apache.ambari.server.security.authorization.LdapServerProperties; -import org.apache.ambari.server.security.authorization.UserType; +import org.apache.ambari.server.security.authorization.UserAuthenticationType; import org.apache.ambari.server.security.authorization.jwt.JwtAuthenticationProperties; import org.apache.ambari.server.security.encryption.CertificateUtils; import org.apache.ambari.server.security.encryption.CredentialProvider; @@ -5997,7 +5997,7 @@ public class Configuration { // Get and process the configured user type values to convert the comma-delimited string of // user types into a ordered (as found in the comma-delimited value) list of UserType values. String userTypes = getProperty(KERBEROS_AUTH_USER_TYPES); - List<UserType> orderedUserTypes = new ArrayList<>(); + List<UserAuthenticationType> orderedUserTypes = new ArrayList<>(); String[] types = userTypes.split(","); for (String type : types) { @@ -6005,7 +6005,7 @@ public class Configuration { if (!type.isEmpty()) { try { - orderedUserTypes.add(UserType.valueOf(type.toUpperCase())); + orderedUserTypes.add(UserAuthenticationType.valueOf(type.toUpperCase())); } catch (IllegalArgumentException e) { String message = String.format("While processing ordered user types from %s, " + "%s was found to be an invalid user type.", @@ -6020,7 +6020,7 @@ public class Configuration { if (orderedUserTypes.isEmpty()) { LOG.info("No (valid) user types were specified in {}. Using the default value of LOCAL.", KERBEROS_AUTH_USER_TYPES.getKey()); - orderedUserTypes.add(UserType.LDAP); + orderedUserTypes.add(UserAuthenticationType.LDAP); } kerberosAuthProperties.setOrderedUserTypes(orderedUserTypes);
http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java index 807bded..f4220bd 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java @@ -119,15 +119,6 @@ public interface AmbariManagementController { String versionTag, Map<String, Map<String, String>> propertiesAttributes); /** - * Creates users. - * - * @param requests the request objects which define the user. - * - * @throws AmbariException when the user cannot be created. - */ - void createUsers(Set<UserRequest> requests) throws AmbariException; - - /** * Creates groups. * * @param requests the request objects which define the groups. @@ -196,18 +187,6 @@ public interface AmbariManagementController { throws AmbariException; /** - * Gets the users identified by the given request objects. - * - * @param requests the request objects - * - * @return a set of user responses - * - * @throws AmbariException if the users could not be read - */ - Set<UserResponse> getUsers(Set<UserRequest> requests) - throws AmbariException, AuthorizationException; - - /** * Gets the user groups identified by the given request objects. * * @param requests the request objects @@ -253,15 +232,6 @@ public interface AmbariManagementController { throws AmbariException, AuthorizationException; /** - * Updates the users specified. - * - * @param requests the users to modify - * - * @throws AmbariException if the resources cannot be updated - */ - void updateUsers(Set<UserRequest> requests) throws AmbariException, AuthorizationException; - - /** * Updates the groups specified. * * @param requests the groups to modify @@ -304,15 +274,6 @@ public interface AmbariManagementController { Set<ServiceComponentHostRequest> requests) throws AmbariException, AuthorizationException; /** - * Deletes the users specified. - * - * @param requests the users to delete - * - * @throws AmbariException if the resources cannot be deleted - */ - void deleteUsers(Set<UserRequest> requests) throws AmbariException; - - /** * Deletes the user groups specified. * * @param requests the groups to delete http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java index 8d262e2..a979845 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java @@ -136,7 +136,6 @@ import org.apache.ambari.server.security.authorization.GroupType; import org.apache.ambari.server.security.authorization.ResourceType; import org.apache.ambari.server.security.authorization.RoleAuthorization; import org.apache.ambari.server.security.authorization.User; -import org.apache.ambari.server.security.authorization.UserType; import org.apache.ambari.server.security.authorization.Users; import org.apache.ambari.server.security.credential.PrincipalKeyCredential; import org.apache.ambari.server.security.encryption.CredentialStoreService; @@ -947,20 +946,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle } @Override - public void createUsers(Set<UserRequest> requests) throws AmbariException { - - for (UserRequest request : requests) { - - if (null == request.getUsername() || request.getUsername().isEmpty() || - null == request.getPassword() || request.getPassword().isEmpty()) { - throw new AmbariException("Username and password must be supplied."); - } - - users.createUser(request.getUsername(), request.getPassword(), UserType.LOCAL, request.isActive(), request.isAdmin()); - } - } - - @Override public void createGroups(Set<GroupRequest> requests) throws AmbariException { for (GroupRequest request : requests) { if (StringUtils.isBlank(request.getGroupName())) { @@ -3405,65 +3390,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle return cluster.getServiceByComponentName(componentName).getName(); } - /** - * Updates the users specified. - * - * @param requests the users to modify - * - * @throws AmbariException if the resources cannot be updated - * @throws IllegalArgumentException if the authenticated user is not authorized to update all of - * the requested properties - */ - @Override - public synchronized void updateUsers(Set<UserRequest> requests) throws AmbariException, AuthorizationException { - boolean isUserAdministrator = AuthorizationHelper.isAuthorized(ResourceType.AMBARI, null, - RoleAuthorization.AMBARI_MANAGE_USERS); - String authenticatedUsername = AuthorizationHelper.getAuthenticatedName(); - - for (UserRequest request : requests) { - String requestedUsername = request.getUsername(); - - // An administrator can modify any user, else a user can only modify themself. - if (!isUserAdministrator && (!authenticatedUsername.equalsIgnoreCase(requestedUsername))) { - throw new AuthorizationException(); - } - - User u = users.getAnyUser(requestedUsername); - if (null == u) { - continue; - } - - if (null != request.isActive()) { - // If this value is being set, make sure the authenticated user is an administrator before - // allowing to change it. Only administrators should be able to change a user's active state - if (!isUserAdministrator) { - throw new AuthorizationException("The authenticated user is not authorized to update the requested resource property"); - } - users.setUserActive(u.getUserName(), request.isActive()); - } - - if (null != request.isAdmin()) { - // If this value is being set, make sure the authenticated user is an administrator before - // allowing to change it. Only administrators should be able to change a user's administrative - // privileges - if (!isUserAdministrator) { - throw new AuthorizationException("The authenticated user is not authorized to update the requested resource property"); - } - - if (request.isAdmin()) { - users.grantAdminPrivilege(u.getUserId()); - } else { - users.revokeAdminPrivilege(u.getUserId()); - } - } - - if (null != request.getOldPassword() && null != request.getPassword()) { - users.modifyPassword(u.getUserName(), request.getOldPassword(), - request.getPassword()); - } - } - } - @Override public synchronized void deleteCluster(ClusterRequest request) throws AmbariException { @@ -3637,21 +3563,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle } @Override - public void deleteUsers(Set<UserRequest> requests) - throws AmbariException { - - for (UserRequest r : requests) { - if (LOG.isDebugEnabled()) { - LOG.debug("Received a delete user request, username={}", r.getUsername()); - } - User u = users.getAnyUser(r.getUsername()); - if (null != u) { - users.removeUser(u); - } - } - } - - @Override public void deleteGroups(Set<GroupRequest> requests) throws AmbariException { for (GroupRequest request: requests) { LOG.debug("Received a delete group request, groupname={}", request.getGroupName()); @@ -3809,64 +3720,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle } @Override - public Set<UserResponse> getUsers(Set<UserRequest> requests) - throws AmbariException, AuthorizationException { - - Set<UserResponse> responses = new HashSet<>(); - - for (UserRequest r : requests) { - - if (LOG.isDebugEnabled()) { - LOG.debug("Received a getUsers request, userRequest={}", r); - } - - String requestedUsername = r.getUsername(); - String authenticatedUsername = AuthorizationHelper.getAuthenticatedName(); - - // A user resource may be retrieved by an administrator or the same user. - if(!AuthorizationHelper.isAuthorized(ResourceType.AMBARI, null, RoleAuthorization.AMBARI_MANAGE_USERS)) { - if (null == requestedUsername) { - // Since the authenticated user is not the administrator, force only that user's resource - // to be returned - requestedUsername = authenticatedUsername; - } else if (!requestedUsername.equalsIgnoreCase(authenticatedUsername)) { - // Since the authenticated user is not the administrator and is asking for a different user, - // throw an AuthorizationException - throw new AuthorizationException(); - } - } - - // get them all - if (null == requestedUsername) { - for (User u : users.getAllUsers()) { - UserResponse resp = new UserResponse(u.getUserName(), u.getUserType(), u.isLdapUser(), u.isActive(), u - .isAdmin()); - resp.setGroups(new HashSet<>(u.getGroups())); - responses.add(resp); - } - } else { - - User u = users.getAnyUser(requestedUsername); - if (null == u) { - if (requests.size() == 1) { - // only throw exceptin if there is a single request - // if there are multiple requests, this indicates an OR predicate - throw new ObjectNotFoundException("Cannot find user '" - + requestedUsername + "'"); - } - } else { - UserResponse resp = new UserResponse(u.getUserName(), u.getUserType(), u.isLdapUser(), u.isActive(), u - .isAdmin()); - resp.setGroups(new HashSet<>(u.getGroups())); - responses.add(resp); - } - } - } - - return responses; - } - - @Override public Set<GroupResponse> getGroups(Set<GroupRequest> requests) throws AmbariException { final Set<GroupResponse> responses = new HashSet<>(); http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java index aeba739..01920f8 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java @@ -90,6 +90,7 @@ import org.apache.ambari.server.orm.dao.ResourceDAO; import org.apache.ambari.server.orm.dao.UserDAO; import org.apache.ambari.server.orm.dao.ViewInstanceDAO; import org.apache.ambari.server.orm.entities.MetainfoEntity; +import org.apache.ambari.server.orm.entities.UserEntity; import org.apache.ambari.server.resources.ResourceManager; import org.apache.ambari.server.resources.api.rest.GetResource; import org.apache.ambari.server.scheduler.ExecutionScheduleManager; @@ -866,8 +867,16 @@ public class AmbariServer { LOG.info("Database init needed - creating default data"); Users users = injector.getInstance(Users.class); - users.createUser("admin", "admin"); - users.createUser("user", "user"); + UserEntity userEntity; + + // Create the admin user + userEntity = users.createUser("admin", "admin", "admin"); + users.addLocalAuthentication(userEntity, "admin"); + users.grantAdminPrivilege(userEntity); + + // Create a normal user + userEntity = users.createUser("user", "user", "user"); + users.addLocalAuthentication(userEntity, "user"); MetainfoEntity schemaVersion = new MetainfoEntity(); schemaVersion.setMetainfoName(Configuration.SERVER_VERSION_KEY); http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java index f3c2ec8..25d12c7 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java @@ -72,6 +72,7 @@ import org.apache.ambari.server.controller.internal.MemberResourceProvider; import org.apache.ambari.server.controller.internal.RepositoryVersionResourceProvider; import org.apache.ambari.server.controller.internal.ServiceResourceProvider; import org.apache.ambari.server.controller.internal.UpgradeResourceProvider; +import org.apache.ambari.server.controller.internal.UserResourceProvider; import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactory; import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactoryImpl; import org.apache.ambari.server.controller.metrics.MetricPropertyProviderFactory; @@ -464,6 +465,7 @@ public class ControllerModule extends AbstractModule { .implement(ResourceProvider.class, Names.named("member"), MemberResourceProvider.class) .implement(ResourceProvider.class, Names.named("repositoryVersion"), RepositoryVersionResourceProvider.class) .implement(ResourceProvider.class, Names.named("hostKerberosIdentity"), HostKerberosIdentityResourceProvider.class) + .implement(ResourceProvider.class, Names.named("user"), UserResourceProvider.class) .implement(ResourceProvider.class, Names.named("credential"), CredentialResourceProvider.class) .implement(ResourceProvider.class, Names.named("kerberosDescriptor"), KerberosDescriptorResourceProvider.class) .implement(ResourceProvider.class, Names.named("upgrade"), UpgradeResourceProvider.class) http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java index 3912138..2454bf7 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java @@ -51,6 +51,11 @@ public interface ResourceProviderFactory { Map<Type, String> keyPropertyIds, AmbariManagementController managementController); + @Named("user") + ResourceProvider getUserResourceProvider(Set<String> propertyIds, + Map<Type, String> keyPropertyIds, + AmbariManagementController managementController); + @Named("hostKerberosIdentity") ResourceProvider getHostKerberosIdentityResourceProvider(AmbariManagementController managementController); http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/UserRequest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/UserRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/UserRequest.java index 40818c8..3011d01 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/UserRequest.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/UserRequest.java @@ -31,15 +31,18 @@ public class UserRequest { private Boolean active; private Boolean admin; - @ApiModelProperty(name = "Users/user_name",hidden = true) - public String getUsername() { - return userName; - } + private String displayName; + private String localUserName; public UserRequest(String name) { this.userName = name; } + @ApiModelProperty(name = "Users/user_name",hidden = true) + public String getUsername() { + return userName; + } + @ApiModelProperty(name = "Users/password") public String getPassword() { return password; @@ -76,6 +79,24 @@ public class UserRequest { this.admin = admin; } + @ApiModelProperty(name = "Users/display_name") + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @ApiModelProperty(name = "Users/local_user_name") + public String getLocalUserName() { + return localUserName; + } + + public void setLocalUserName(String localUserName) { + this.localUserName = localUserName; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/UserResponse.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/UserResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/UserResponse.java index 5afacb7..bcb3aaf 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/UserResponse.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/UserResponse.java @@ -20,25 +20,26 @@ package org.apache.ambari.server.controller; import java.util.Collections; import java.util.Set; -import org.apache.ambari.server.security.authorization.UserType; +import org.apache.ambari.server.security.authorization.UserAuthenticationType; import io.swagger.annotations.ApiModelProperty; /** * Represents a user maintenance request. */ -public class UserResponse implements ApiModel { +public class +UserResponse implements ApiModel { private final String userName; - private final UserType userType; + private final UserAuthenticationType authenticationType; private final boolean isLdapUser; private final boolean isActive; private final boolean isAdmin; private Set<String> groups = Collections.emptySet(); - public UserResponse(String userName, UserType userType, boolean isLdapUser, boolean isActive, boolean isAdmin) { + public UserResponse(String userName, UserAuthenticationType userType, boolean isLdapUser, boolean isActive, boolean isAdmin) { this.userName = userName; - this.userType = userType; + this.authenticationType = userType; this.isLdapUser = isLdapUser; this.isActive = isActive; this.isAdmin = isAdmin; @@ -49,7 +50,7 @@ public class UserResponse implements ApiModel { this.isLdapUser = isLdapUser; this.isActive = isActive; this.isAdmin = isAdmin; - this.userType = UserType.LOCAL; + this.authenticationType = UserAuthenticationType.LOCAL; } @ApiModelProperty(name = "Users/user_name",required = true) @@ -84,9 +85,9 @@ public class UserResponse implements ApiModel { return isAdmin; } - @ApiModelProperty(name = "Users/user_type") - public UserType getUserType() { - return userType; + @ApiModelProperty(name = "Users/authentication_type") + public UserAuthenticationType getAuthenticationType() { + return authenticationType; } @Override @@ -97,14 +98,14 @@ public class UserResponse implements ApiModel { UserResponse that = (UserResponse) o; if (userName != null ? !userName.equals(that.userName) : that.userName != null) return false; - return userType == that.userType; + return authenticationType == that.authenticationType; } @Override public int hashCode() { int result = userName != null ? userName.hashCode() : 0; - result = 31 * result + (userType != null ? userType.hashCode() : 0); + result = 31 * result + (authenticationType != null ? authenticationType.hashCode() : 0); return result; } } http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java index b35b2a8..595b7f9 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java @@ -171,7 +171,7 @@ public abstract class AbstractControllerResourceProvider extends AbstractAuthori case Task: return new TaskResourceProvider(propertyIds, keyPropertyIds, managementController); case User: - return new UserResourceProvider(propertyIds, keyPropertyIds, managementController); + return resourceProviderFactory.getUserResourceProvider(propertyIds, keyPropertyIds, managementController); case Group: return new GroupResourceProvider(propertyIds, keyPropertyIds, managementController); case Member: http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActiveWidgetLayoutResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActiveWidgetLayoutResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActiveWidgetLayoutResourceProvider.java index 389f0b2..a0a5e38 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActiveWidgetLayoutResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActiveWidgetLayoutResourceProvider.java @@ -160,7 +160,7 @@ public class ActiveWidgetLayoutResourceProvider extends AbstractControllerResour } java.lang.reflect.Type type = new TypeToken<Set<Map<String, String>>>(){}.getType(); - Set<Map<String, String>> activeWidgetLayouts = gson.fromJson(userDAO.findSingleUserByName(userName).getActiveWidgetLayouts(), type); + Set<Map<String, String>> activeWidgetLayouts = gson.fromJson(userDAO.findUserByName(userName).getActiveWidgetLayouts(), type); if (activeWidgetLayouts != null) { for (Map<String, String> widgetLayoutId : activeWidgetLayouts) { layoutEntities.add(widgetLayoutDAO.findById(Long.parseLong(widgetLayoutId.get(ID)))); http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserPrivilegeResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserPrivilegeResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserPrivilegeResourceProvider.java index 614f7ab..816767e 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserPrivilegeResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserPrivilegeResourceProvider.java @@ -51,7 +51,6 @@ import org.apache.ambari.server.security.authorization.AuthorizationException; import org.apache.ambari.server.security.authorization.AuthorizationHelper; import org.apache.ambari.server.security.authorization.ResourceType; import org.apache.ambari.server.security.authorization.RoleAuthorization; -import org.apache.ambari.server.security.authorization.UserType; import org.apache.ambari.server.security.authorization.Users; import com.google.common.cache.CacheBuilder; @@ -187,14 +186,7 @@ public class UserPrivilegeResourceProvider extends ReadOnlyResourceProvider { @Override public UserEntity load(String key) throws Exception { //fallback mechanism, mostly for unit tests - UserEntity userEntity = userDAO.findLocalUserByName(key); - if (userEntity == null) { - userEntity = userDAO.findLdapUserByName(key); - } - if (userEntity == null) { - userEntity = userDAO.findUserByNameAndType(key, UserType.JWT); - } - return userEntity; + return userDAO.findUserByName(key); } }; @@ -281,9 +273,7 @@ public class UserPrivilegeResourceProvider extends ReadOnlyResourceProvider { Map<String, UserEntity> userNames = new TreeMap<>(); for (UserEntity entity : userDAO.findAll()) { UserEntity existing = userNames.get(entity.getUserName()); - if (existing == null || - entity.getUserType() == UserType.LOCAL || - existing.getUserType() == UserType.JWT) { + if (existing == null) { userNames.put(entity.getUserName(), entity); } } @@ -292,10 +282,12 @@ public class UserPrivilegeResourceProvider extends ReadOnlyResourceProvider { } if (userEntity == null) { - userEntity = userDAO.findUserByNameAndType(userName, UserType.PAM); + userEntity = userDAO.findUserByName(userName); } + if (userEntity == null) { - throw new SystemException("User " + userName + " was not found"); + LOG.debug("User {} was not found", userName); + throw new SystemException("User was not found"); } final Collection<PrivilegeEntity> privileges = users.getUserPrivileges(userEntity); http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserResourceProvider.java index c5c36e9..45b733b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UserResourceProvider.java @@ -20,10 +20,12 @@ package org.apache.ambari.server.controller.internal; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.apache.ambari.server.AmbariException; +import org.apache.ambari.server.ObjectNotFoundException; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.UserRequest; import org.apache.ambari.server.controller.UserResponse; @@ -39,8 +41,20 @@ import org.apache.ambari.server.controller.spi.ResourcePredicateEvaluator; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; import org.apache.ambari.server.controller.utilities.PropertyHelper; +import org.apache.ambari.server.orm.entities.MemberEntity; +import org.apache.ambari.server.orm.entities.UserAuthenticationEntity; +import org.apache.ambari.server.orm.entities.UserEntity; import org.apache.ambari.server.security.authorization.AuthorizationException; +import org.apache.ambari.server.security.authorization.AuthorizationHelper; +import org.apache.ambari.server.security.authorization.ResourceType; import org.apache.ambari.server.security.authorization.RoleAuthorization; +import org.apache.ambari.server.security.authorization.UserAuthenticationType; +import org.apache.ambari.server.security.authorization.Users; +import org.apache.commons.lang.StringUtils; + +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; /** * Resource provider for user resources. @@ -50,25 +64,31 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp // ----- Property ID constants --------------------------------------------- // Users - public static final String USER_USERNAME_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "user_name"); - public static final String USER_PASSWORD_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "password"); + public static final String USER_USERNAME_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "user_name"); + public static final String USER_PASSWORD_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "password"); public static final String USER_OLD_PASSWORD_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "old_password"); - public static final String USER_LDAP_USER_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "ldap_user"); - public static final String USER_TYPE_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "user_type"); - public static final String USER_ACTIVE_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "active"); - public static final String USER_GROUPS_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "groups"); - public static final String USER_ADMIN_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "admin"); + @Deprecated + public static final String USER_LDAP_USER_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "ldap_user"); + @Deprecated + public static final String USER_TYPE_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "user_type"); + public static final String USER_ACTIVE_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "active"); + public static final String USER_GROUPS_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "groups"); + public static final String USER_ADMIN_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "admin"); private static Set<String> pkPropertyIds = - new HashSet<>(Arrays.asList(new String[]{ - USER_USERNAME_PROPERTY_ID})); + new HashSet<>(Arrays.asList(new String[]{ + USER_USERNAME_PROPERTY_ID})); + + @Inject + private Users users; /** * Create a new resource provider for the given management controller. */ - UserResourceProvider(Set<String> propertyIds, - Map<Resource.Type, String> keyPropertyIds, - AmbariManagementController managementController) { + @AssistedInject + UserResourceProvider(@Assisted Set<String> propertyIds, + @Assisted Map<Resource.Type, String> keyPropertyIds, + @Assisted AmbariManagementController managementController) { super(propertyIds, keyPropertyIds, managementController); setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.AMBARI_MANAGE_USERS)); @@ -89,7 +109,7 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp createResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { - getManagementController().createUsers(requests); + createUsers(requests); return null; } }); @@ -114,7 +134,7 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp Set<UserResponse> responses = getResources(new Command<Set<UserResponse>>() { @Override public Set<UserResponse> invoke() throws AmbariException, AuthorizationException { - return getManagementController().getUsers(requests); + return getUsers(requests); } }); @@ -122,8 +142,8 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp LOG.debug("Found user responses matching get user request, userRequestSize={}, userResponseSize={}", requests.size(), responses.size()); } - Set<String> requestedIds = getRequestPropertyIds(request, predicate); - Set<Resource> resources = new HashSet<>(); + Set<String> requestedIds = getRequestPropertyIds(request, predicate); + Set<Resource> resources = new HashSet<>(); for (UserResponse userResponse : responses) { ResourceImpl resource = new ResourceImpl(Resource.Type.User); @@ -131,11 +151,13 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp setResourceProperty(resource, USER_USERNAME_PROPERTY_ID, userResponse.getUsername(), requestedIds); + // This is deprecated but here for backwards compatibility setResourceProperty(resource, USER_LDAP_USER_PROPERTY_ID, userResponse.isLdapUser(), requestedIds); + // This is deprecated but here for backwards compatibility setResourceProperty(resource, USER_TYPE_PROPERTY_ID, - userResponse.getUserType(), requestedIds); + userResponse.getAuthenticationType(), requestedIds); setResourceProperty(resource, USER_ACTIVE_PROPERTY_ID, userResponse.isActive(), requestedIds); @@ -154,7 +176,7 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp @Override public RequestStatus updateResources(Request request, Predicate predicate) - throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { + throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { final Set<UserRequest> requests = new HashSet<>(); for (Map<String, Object> propertyMap : getPropertyMaps(request.getProperties().iterator().next(), predicate)) { @@ -166,7 +188,7 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException, AuthorizationException { - getManagementController().updateUsers(requests); + updateUsers(requests); return null; } }); @@ -188,7 +210,7 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp modifyResources(new Command<Void>() { @Override public Void invoke() throws AmbariException { - getManagementController().deleteUsers(requests); + deleteUsers(requests); return null; } }); @@ -201,15 +223,14 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp * we do a case insensitive comparison so that we can return the retrieved * username when it differs only in case with respect to the requested username. * - * @param predicate the predicate - * @param resource the resource - * - * @return - */ + * @param predicate the predicate + * @param resource the resource + * @return + */ @Override public boolean evaluate(Predicate predicate, Resource resource) { if (predicate instanceof EqualsPredicate) { - EqualsPredicate equalsPredicate = (EqualsPredicate)predicate; + EqualsPredicate equalsPredicate = (EqualsPredicate) predicate; String propertyId = equalsPredicate.getPropertyId(); if (propertyId.equals(USER_USERNAME_PROPERTY_ID)) { return equalsPredicate.evaluateIgnoreCase(resource); @@ -228,7 +249,7 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp return new UserRequest(null); } - UserRequest request = new UserRequest ((String) properties.get(USER_USERNAME_PROPERTY_ID)); + UserRequest request = new UserRequest((String) properties.get(USER_USERNAME_PROPERTY_ID)); request.setPassword((String) properties.get(USER_PASSWORD_PROPERTY_ID)); request.setOldPassword((String) properties.get(USER_OLD_PASSWORD_PROPERTY_ID)); @@ -243,4 +264,197 @@ public class UserResourceProvider extends AbstractControllerResourceProvider imp return request; } + + + /** + * Creates users. + * + * @param requests the request objects which define the user. + * @throws AmbariException when the user cannot be created. + */ + private void createUsers(Set<UserRequest> requests) throws AmbariException { + for (UserRequest request : requests) { + String username = request.getUsername(); + String password = request.getPassword(); + + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { + throw new AmbariException("Username and password must be supplied."); + } + + String displayName = StringUtils.defaultIfEmpty(request.getDisplayName(), username); + String localUserName = StringUtils.defaultIfEmpty(request.getLocalUserName(), username); + + UserEntity userEntity = users.createUser(username, localUserName, displayName, request.isActive()); + if (userEntity != null) { + users.addLocalAuthentication(userEntity, password); + + if (Boolean.TRUE.equals(request.isAdmin())) { + users.grantAdminPrivilege(userEntity); + } + } + } + } + + /** + * Updates the users specified. + * + * @param requests the users to modify + * @throws AmbariException if the resources cannot be updated + * @throws IllegalArgumentException if the authenticated user is not authorized to update all of + * the requested properties + */ + private void updateUsers(Set<UserRequest> requests) throws AmbariException, AuthorizationException { + boolean isUserAdministrator = AuthorizationHelper.isAuthorized(ResourceType.AMBARI, null, + RoleAuthorization.AMBARI_MANAGE_USERS); + String authenticatedUsername = AuthorizationHelper.getAuthenticatedName(); + + for (UserRequest request : requests) { + String requestedUsername = request.getUsername(); + + // An administrator can modify any user, else a user can only modify themself. + if (!isUserAdministrator && (!authenticatedUsername.equalsIgnoreCase(requestedUsername))) { + throw new AuthorizationException(); + } + + UserEntity userEntity = users.getUserEntity(requestedUsername); + if (null == userEntity) { + continue; + } + + if (null != request.isActive()) { + // If this value is being set, make sure the authenticated user is an administrator before + // allowing to change it. Only administrators should be able to change a user's active state + if (!isUserAdministrator) { + throw new AuthorizationException("The authenticated user is not authorized to update the requested resource property"); + } + users.setUserActive(userEntity, request.isActive()); + } + + if (null != request.isAdmin()) { + // If this value is being set, make sure the authenticated user is an administrator before + // allowing to change it. Only administrators should be able to change a user's administrative + // privileges + if (!isUserAdministrator) { + throw new AuthorizationException("The authenticated user is not authorized to update the requested resource property"); + } + + if (request.isAdmin()) { + users.grantAdminPrivilege(userEntity); + } else { + users.revokeAdminPrivilege(userEntity); + } + } + + if (null != request.getOldPassword() && null != request.getPassword()) { + users.modifyPassword(userEntity, request.getOldPassword(), request.getPassword()); + } + } + } + + /** + * Deletes the users specified. + * + * @param requests the users to delete + * @throws AmbariException if the resources cannot be deleted + */ + private void deleteUsers(Set<UserRequest> requests) + throws AmbariException { + + for (UserRequest r : requests) { + String username = r.getUsername(); + if (!StringUtils.isEmpty(username)) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Received a delete user request, username= {}", username); + } + + users.removeUser(users.getUserEntity(username)); + } + } + } + + /** + * Gets the users identified by the given request objects. + * + * @param requests the request objects + * @return a set of user responses + * @throws AmbariException if the users could not be read + */ + private Set<UserResponse> getUsers(Set<UserRequest> requests) + throws AmbariException, AuthorizationException { + + Set<UserResponse> responses = new HashSet<>(); + + for (UserRequest r : requests) { + + if (LOG.isDebugEnabled()) { + LOG.debug("Received a getUsers request, userRequest={}", r.toString()); + } + + String requestedUsername = r.getUsername(); + String authenticatedUsername = AuthorizationHelper.getAuthenticatedName(); + + // A user resource may be retrieved by an administrator or the same user. + if (!AuthorizationHelper.isAuthorized(ResourceType.AMBARI, null, RoleAuthorization.AMBARI_MANAGE_USERS)) { + if (null == requestedUsername) { + // Since the authenticated user is not the administrator, force only that user's resource + // to be returned + requestedUsername = authenticatedUsername; + } else if (!requestedUsername.equalsIgnoreCase(authenticatedUsername)) { + // Since the authenticated user is not the administrator and is asking for a different user, + // throw an AuthorizationException + throw new AuthorizationException(); + } + } + + // get them all + if (null == requestedUsername) { + for (UserEntity u : users.getAllUserEntities()) { + responses.add(createUserResponse(u)); + } + } else { + + UserEntity u = users.getUserEntity(requestedUsername); + if (null == u) { + if (requests.size() == 1) { + // only throw exceptin if there is a single request + // if there are multiple requests, this indicates an OR predicate + throw new ObjectNotFoundException("Cannot find user '" + + requestedUsername + "'"); + } + } else { + responses.add(createUserResponse(u)); + } + } + } + + return responses; + } + + private UserResponse createUserResponse(UserEntity userEntity) { + List<UserAuthenticationEntity> authenticationEntities = userEntity.getAuthenticationEntities(); + boolean isLdapUser = false; + UserAuthenticationType userType = UserAuthenticationType.LOCAL; + + for (UserAuthenticationEntity authenticationEntity : authenticationEntities) { + if (authenticationEntity.getAuthenticationType() == UserAuthenticationType.LDAP) { + isLdapUser = true; + userType = UserAuthenticationType.LDAP; + } else if (authenticationEntity.getAuthenticationType() == UserAuthenticationType.PAM) { + userType = UserAuthenticationType.PAM; + } + } + + Set<String> groups = new HashSet<>(); + for (MemberEntity memberEntity : userEntity.getMemberEntities()) { + groups.add(memberEntity.getGroup().getGroupName()); + } + + boolean isAdmin = users.hasAdminPrivilege(userEntity); + + UserResponse userResponse = new UserResponse(userEntity.getUserName(), userType, isLdapUser, userEntity.getActive(), isAdmin); + userResponse.setGroups(groups); + return userResponse; + } + } http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserAuthenticationDAO.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserAuthenticationDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserAuthenticationDAO.java new file mode 100644 index 0000000..5ecff52 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserAuthenticationDAO.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.orm.dao; + +import java.util.List; +import java.util.Set; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import org.apache.ambari.server.orm.RequiresSession; +import org.apache.ambari.server.orm.entities.UserAuthenticationEntity; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.persist.Transactional; + +@Singleton +public class UserAuthenticationDAO { + + @Inject + Provider<EntityManager> entityManagerProvider; + @Inject + DaoUtils daoUtils; + + @RequiresSession + public UserAuthenticationEntity findByPK(Long pk) { + return entityManagerProvider.get().find(UserAuthenticationEntity.class, pk); + } + + @RequiresSession + public List<UserAuthenticationEntity> findAll() { + TypedQuery<UserAuthenticationEntity> query = entityManagerProvider.get().createNamedQuery("UserAuthenticationEntity.findAll", UserAuthenticationEntity.class); + return daoUtils.selectList(query); + } + + @Transactional + public void create(UserAuthenticationEntity entity) { + entityManagerProvider.get().persist(entity); + } + + @Transactional + public void create(Set<UserAuthenticationEntity> entities) { + for (UserAuthenticationEntity entity : entities) { + entityManagerProvider.get().persist(entity); + } + } + + @Transactional + public UserAuthenticationEntity merge(UserAuthenticationEntity entity) { + return entityManagerProvider.get().merge(entity); + } + + @Transactional + public void merge(Set<UserAuthenticationEntity> entities) { + for (UserAuthenticationEntity entity : entities) { + entityManagerProvider.get().merge(entity); + } + } + + @Transactional + public void remove(UserAuthenticationEntity entity) { + entityManagerProvider.get().remove(entity); + } + + @Transactional + public void remove(Set<UserAuthenticationEntity> entities) { + for (UserAuthenticationEntity entity : entities) { + entityManagerProvider.get().remove(entity); + } + } + + @Transactional + public void removeByPK(Long pk) { + remove(findByPK(pk)); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java index ce47c4c..0e28e50 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java @@ -17,13 +17,11 @@ */ package org.apache.ambari.server.orm.dao; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.annotation.Nullable; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; @@ -31,9 +29,7 @@ import javax.persistence.TypedQuery; import org.apache.ambari.server.orm.RequiresSession; import org.apache.ambari.server.orm.entities.PrincipalEntity; import org.apache.ambari.server.orm.entities.UserEntity; -import org.apache.ambari.server.security.authorization.UserType; -import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -58,12 +54,7 @@ public class UserDAO { return daoUtils.selectList(query); } - /** - * Results in Exception if two users with same name but different types present in DB - * As such situation is valid, use {@link #findUserByNameAndType(String, UserType)} instead - */ @RequiresSession - @Deprecated public UserEntity findUserByName(String userName) { TypedQuery<UserEntity> query = entityManagerProvider.get().createNamedQuery("userByName", UserEntity.class); query.setParameter("username", userName.toLowerCase()); @@ -75,81 +66,9 @@ public class UserDAO { } /** - * <p>Finds user by name. If duplicate users exists (with different type), the returned one will be chosen by this user - * type precedence: LOCAL -> LDAP -> JWT -> PAM</p> - * <p>In Ambari 3.0, user management will be rethought hence the deprecation</p> - * @param userName the user name - * @return The corresponding user or {@code null} if none is found. If multiple users exist with different types, user - * type precedence (see above) will decide. - */ - @RequiresSession - @Deprecated - @Nullable - public UserEntity findSingleUserByName(String userName) { - TypedQuery<UserEntity> query = entityManagerProvider.get().createNamedQuery("userByName", UserEntity.class); - query.setParameter("username", userName.toLowerCase()); - List<UserEntity> resultList = query.getResultList(); - switch (resultList.size()) { - case 0: - return null; - case 1: - return resultList.get(0); - default: - ImmutableMap.Builder<UserType, UserEntity> mapBuilder = ImmutableMap.builder(); - for (UserEntity user: resultList) { - mapBuilder.put(user.getUserType(), user); - } - ImmutableMap<UserType, UserEntity> usersByType = mapBuilder.build(); - UserEntity user = - usersByType.containsKey(UserType.LOCAL) ? usersByType.get(UserType.LOCAL) : - usersByType.containsKey(UserType.LOCAL.LDAP) ? usersByType.get(UserType.LDAP) : - usersByType.containsKey(UserType.JWT) ? usersByType.get(UserType.JWT) : - usersByType.get(UserType.PAM); - return user; - } - } - - - @RequiresSession - public UserEntity findUserByNameAndType(String userName, UserType userType) { - TypedQuery<UserEntity> query = entityManagerProvider.get().createQuery("SELECT user FROM UserEntity user WHERE " + - "user.userType=:type AND lower(user.userName)=lower(:name)", UserEntity.class); // do case insensitive compare - query.setParameter("type", userType); - query.setParameter("name", userName); - try { - return query.getSingleResult(); - } catch (NoResultException e) { - return null; - } - } - - @RequiresSession - public UserEntity findLocalUserByName(String userName) { - TypedQuery<UserEntity> query = entityManagerProvider.get().createNamedQuery("localUserByName", UserEntity.class); - query.setParameter("username", userName.toLowerCase()); - try { - return query.getSingleResult(); - } catch (NoResultException e) { - return null; - } - } - - @RequiresSession - public UserEntity findLdapUserByName(String userName) { - TypedQuery<UserEntity> query = entityManagerProvider.get().createNamedQuery("ldapUserByName", UserEntity.class); - query.setParameter("username", userName.toLowerCase()); - try { - return query.getSingleResult(); - } catch (NoResultException e) { - return null; - } - } - - /** * Find the user entities for the given list of admin principal entities. * - * @param principalList the list of principal entities - * + * @param principalList the list of principal entities * @return the matching list of user entities */ @RequiresSession @@ -166,7 +85,6 @@ public class UserDAO { * Find the user entity for the given admin principal entity. * * @param principal the principal entity - * * @return the matching user entity */ @RequiresSession @@ -182,27 +100,24 @@ public class UserDAO { @Transactional public void create(UserEntity user) { - create(new HashSet<>(Arrays.asList(user))); + create(new HashSet<>(Collections.singleton(user))); } @Transactional public void create(Set<UserEntity> users) { for (UserEntity user: users) { -// user.setUserName(user.getUserName().toLowerCase()); entityManagerProvider.get().persist(user); } } @Transactional public UserEntity merge(UserEntity user) { -// user.setUserName(user.getUserName().toLowerCase()); return entityManagerProvider.get().merge(user); } @Transactional public void merge(Set<UserEntity> users) { - for (UserEntity user: users) { -// user.setUserName(user.getUserName().toLowerCase()); + for (UserEntity user : users) { entityManagerProvider.get().merge(user); } } @@ -215,7 +130,7 @@ public class UserDAO { @Transactional public void remove(Set<UserEntity> users) { - for (UserEntity userEntity: users) { + for (UserEntity userEntity : users) { entityManagerProvider.get().remove(entityManagerProvider.get().merge(userEntity)); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserAuthenticationEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserAuthenticationEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserAuthenticationEntity.java new file mode 100644 index 0000000..ffb8e6d --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserAuthenticationEntity.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.orm.entities; + +import java.util.Date; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.TableGenerator; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.ambari.server.security.authorization.UserAuthenticationType; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +@Table(name = "user_authentication") +@Entity +@NamedQueries({ + @NamedQuery(name = "UserAuthenticationEntity.findAll", query = "SELECT entity FROM UserAuthenticationEntity entity") +}) +@TableGenerator(name = "user_authentication_id_generator", + table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value" + , pkColumnValue = "user_authentication_id_seq" + , initialValue = 2 + , allocationSize = 500 +) +public class UserAuthenticationEntity { + + @Id + @Column(name = "user_authentication_id") + @GeneratedValue(strategy = GenerationType.TABLE, generator = "user_authentication_id_generator") + private Long userAuthenticationId; + + @Column(name = "authentication_type", nullable = false) + @Enumerated(EnumType.STRING) + @Basic + private UserAuthenticationType authenticationType = UserAuthenticationType.LOCAL; + + @Column(name = "authentication_key") + @Lob + @Basic + private byte[] authenticationKey; + + @Column(name = "create_time", nullable = false) + @Basic + @Temporal(value = TemporalType.TIMESTAMP) + private Date createTime = new Date(); + + @Column(name = "update_time", nullable = false) + @Basic + @Temporal(value = TemporalType.TIMESTAMP) + private Date updateTime = new Date(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false) + private UserEntity user; + + public Long getUserAuthenticationId() { + return userAuthenticationId; + } + + public void setUserAuthenticationId(Long userAuthenticationId) { + this.userAuthenticationId = userAuthenticationId; + } + + public UserAuthenticationType getAuthenticationType() { + return authenticationType; + } + + public void setAuthenticationType(UserAuthenticationType authenticationType) { + this.authenticationType = authenticationType; + } + + public String getAuthenticationKey() { + return authenticationKey == null ? "" : new String(authenticationKey); + } + + public void setAuthenticationKey(String authenticationKey) { + this.authenticationKey = (authenticationKey == null) ? null : authenticationKey.getBytes(); + } + + public Date getCreateTime() { + return createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + /** + * Get the relevant {@link UserEntity} associated with this {@link UserAuthenticationEntity}. + * + * @return a {@link UserEntity} + */ + public UserEntity getUser() { + return user; + } + + /** + * Set the relevant {@link UserEntity} associated with this {@link UserAuthenticationEntity}. + * + * @param user a {@link UserEntity} + */ + public void setUser(UserEntity user) { + this.user = user; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o == null || getClass() != o.getClass()) { + return false; + } else { + UserAuthenticationEntity that = (UserAuthenticationEntity) o; + + EqualsBuilder equalsBuilder = new EqualsBuilder(); + equalsBuilder.append(userAuthenticationId, that.userAuthenticationId); + equalsBuilder.append(authenticationType, that.authenticationType); + equalsBuilder.append(authenticationKey, that.authenticationKey); + equalsBuilder.append(createTime, that.createTime); + equalsBuilder.append(updateTime, that.updateTime); + return equalsBuilder.isEquals(); + } + } + + @Override + public int hashCode() { + HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); + hashCodeBuilder.append(userAuthenticationId); + hashCodeBuilder.append(authenticationType); + hashCodeBuilder.append(authenticationKey); + hashCodeBuilder.append(createTime); + hashCodeBuilder.append(updateTime); + return hashCodeBuilder.toHashCode(); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java index 9011eae..66e9003 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java @@ -17,16 +17,17 @@ */ package org.apache.ambari.server.orm.entities; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -42,27 +43,21 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.UniqueConstraint; -import org.apache.ambari.server.security.authorization.UserName; -import org.apache.ambari.server.security.authorization.UserType; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; -@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_name", "user_type"})}) +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_name"})}) @Entity @NamedQueries({ @NamedQuery(name = "userByName", query = "SELECT user_entity from UserEntity user_entity " + - "where lower(user_entity.userName)=:username"), - @NamedQuery(name = "localUserByName", query = "SELECT user_entity FROM UserEntity user_entity " + - "where lower(user_entity.userName)=:username AND " + - "user_entity.userType=org.apache.ambari.server.security.authorization.UserType.LOCAL"), - @NamedQuery(name = "ldapUserByName", query = "SELECT user_entity FROM UserEntity user_entity " + - "where lower(user_entity.userName)=:username AND " + - "user_entity.userType=org.apache.ambari.server.security.authorization.UserType.LDAP") + "where lower(user_entity.userName)=lower(:username)") }) @TableGenerator(name = "user_id_generator", table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value" , pkColumnValue = "user_id_seq" , initialValue = 2 , allocationSize = 500 - ) +) public class UserEntity { @Id @@ -70,40 +65,41 @@ public class UserEntity { @GeneratedValue(strategy = GenerationType.TABLE, generator = "user_id_generator") private Integer userId; - @Column(name = "user_name") + @Column(name = "user_name", nullable = false) private String userName; - @Column(name = "ldap_user") - private Integer ldapUser = 0; - - @Column(name = "user_type") - @Enumerated(EnumType.STRING) - @Basic - private UserType userType = UserType.LOCAL; - - @Column(name = "user_password") - @Basic - private String userPassword; - - @Column(name = "create_time") + @Column(name = "create_time", nullable = false) @Basic @Temporal(value = TemporalType.TIMESTAMP) private Date createTime = new Date(); - @Column(name = "active") + @Column(name = "active", nullable = false) private Integer active = 1; + @Column(name = "consecutive_failures", nullable = false) + private Integer consecutiveFailures = 0; + + @Column(name = "display_name") + private String displayName; + + @Column(name = "local_username") + private String localUsername; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private Set<MemberEntity> memberEntities = new HashSet<>(); @OneToOne @JoinColumns({ - @JoinColumn(name = "principal_id", referencedColumnName = "principal_id", nullable = false), + @JoinColumn(name = "principal_id", referencedColumnName = "principal_id", nullable = false) }) private PrincipalEntity principal; @Column(name = "active_widget_layouts") private String activeWidgetLayouts; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List<UserAuthenticationEntity> authenticationEntities = new ArrayList<>(); + // ----- UserEntity -------------------------------------------------------- public Integer getUserId() { @@ -118,37 +114,96 @@ public class UserEntity { return userName; } - public void setUserName(UserName userName) { - this.userName = userName.toString(); + public void setUserName(String userName) { + // Force the username to be lowercase + this.userName = (userName == null) ? null : userName.toLowerCase(); + } + + /** + * Returns the number of consecutive authentication failures since the last successful login. + * <p> + * This value may be used to throttle authentication attempts or lock out users. It is expected that + * this value is reset to <code>0</code> when a successful authentication attempt was made. + * + * @return the number of consecutive authentication failures since the last successful login + */ + public Integer getConsecutiveFailures() { + return consecutiveFailures; } - public Boolean getLdapUser() { - return ldapUser == 0 ? Boolean.FALSE : Boolean.TRUE; + /** + * Sets the number of consecutive authentication failures since the last successful login. + * <p> + * This value may be used to throttle authentication attempts or lock out users. It is expected that + * this value is reset to <code>0</code> when a successful authentication attempt was made. + * <p> + * For each failed authentication attempt, {@link #incrementConsecutiveFailures()} should be called + * rather than explicitly setting an incremented value. + * + * @param consecutiveFailures a number of consecutive authentication failures since the last successful login + */ + public void setConsecutiveFailures(Integer consecutiveFailures) { + this.consecutiveFailures = consecutiveFailures; } - public void setLdapUser(Boolean ldapUser) { - if (ldapUser == null) { - this.ldapUser = null; - } else { - this.ldapUser = ldapUser ? 1 : 0; - this.userType = ldapUser ? UserType.LDAP : UserType.LOCAL; - } + /** + * Increments the number of consecutive authentication failures since the last successful login. + * <p> + * This value may be used to throttle authentication attempts or lock out users. It is expected that + * this value is reset to <code>0</code> when a successful authentication attempt was made. + * <p> + * TODO: Ensure that this value is consistent when updating concurrently + */ + public void incrementConsecutiveFailures() { + this.consecutiveFailures++; } - public UserType getUserType() { - return userType; + /** + * Returns the display name for this user. + * <p> + * This value may be used in user interfaces rather than the username to show who it logged in. If + * empty, it is expected that the user's {@link #userName} value would be used instead. + * + * @return the user's display name + */ + public String getDisplayName() { + return displayName; } - public void setUserType(UserType userType) { - this.userType = userType; + /** + * Sets the display name for this user. + * <p> + * This value may be used in user interfaces rather than the username to show who it logged in. If + * empty, it is expected that the user's {@link #userName} value would be used instead. + * + * @param displayName the user's display name + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; } - public String getUserPassword() { - return userPassword; + /** + * Gets the local username for this user. + * <p> + * This value is intended to be used when accessing services via Ambari Views. If + * empty, it is expected that the user's {@link #userName} value would be used instead. + * + * @return the user's local username + */ + public String getLocalUsername() { + return localUsername; } - public void setUserPassword(String userPassword) { - this.userPassword = userPassword; + /** + * Sets the local username for this user. + * <p> + * This value is intended to be used when accessing services via Ambari Views. If + * empty, it is expected that the user's {@link #userName} value would be used instead. + * + * @param localUsername the user's local username + */ + public void setLocalUsername(String localUsername) { + this.localUsername = localUsername; } public Date getCreateTime() { @@ -191,7 +246,7 @@ public class UserEntity { /** * Set the admin principal entity. * - * @param principal the principal entity + * @param principal the principal entity */ public void setPrincipal(PrincipalEntity principal) { this.principal = principal; @@ -205,35 +260,57 @@ public class UserEntity { this.activeWidgetLayouts = activeWidgetLayouts; } -// ----- Object overrides -------------------------------------------------- - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + public List<UserAuthenticationEntity> getAuthenticationEntities() { + return authenticationEntities; + } - UserEntity that = (UserEntity) o; + public void setAuthenticationEntities(List<UserAuthenticationEntity> authenticationEntities) { + // If the passed in value is not the same list that is stored internally, clear it and set the + // entries to the same set that the user passed in. + // If the passed in value is the same list, then do nothing since the internal value is already + // set. + if (this.authenticationEntities != authenticationEntities) { // Tests to see if the Lists are the same object, not if they have the same content. + this.authenticationEntities.clear(); + + if (authenticationEntities != null) { + this.authenticationEntities.addAll(authenticationEntities); + } + } + } - if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; - if (createTime != null ? !createTime.equals(that.createTime) : that.createTime != null) return false; - if (ldapUser != null ? !ldapUser.equals(that.ldapUser) : that.ldapUser != null) return false; - if (userType != null ? !userType.equals(that.userType) : that.userType != null) return false; - if (userName != null ? !userName.equals(that.userName) : that.userName != null) return false; - if (userPassword != null ? !userPassword.equals(that.userPassword) : that.userPassword != null) return false; - if (active != null ? !active.equals(that.active) : that.active != null) return false; + // ----- Object overrides -------------------------------------------------- - return true; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o == null || getClass() != o.getClass()) { + return false; + } else { + UserEntity that = (UserEntity) o; + + EqualsBuilder equalsBuilder = new EqualsBuilder(); + equalsBuilder.append(userId, that.userId); + equalsBuilder.append(userName, that.userName); + equalsBuilder.append(displayName, that.displayName); + equalsBuilder.append(localUsername, that.localUsername); + equalsBuilder.append(consecutiveFailures, that.consecutiveFailures); + equalsBuilder.append(active, that.active); + equalsBuilder.append(createTime, that.createTime); + return equalsBuilder.isEquals(); + } } @Override public int hashCode() { - int result = userId != null ? userId.hashCode() : 0; - result = 31 * result + (userName != null ? userName.hashCode() : 0); - result = 31 * result + (userPassword != null ? userPassword.hashCode() : 0); - result = 31 * result + (ldapUser != null ? ldapUser.hashCode() : 0); - result = 31 * result + (userType != null ? userType.hashCode() : 0); - result = 31 * result + (createTime != null ? createTime.hashCode() : 0); - result = 31 * result + (active != null ? active.hashCode() : 0); - return result; + HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); + hashCodeBuilder.append(userId); + hashCodeBuilder.append(userName); + hashCodeBuilder.append(displayName); + hashCodeBuilder.append(localUsername); + hashCodeBuilder.append(consecutiveFailures); + hashCodeBuilder.append(active); + hashCodeBuilder.append(createTime); + return hashCodeBuilder.toHashCode(); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariJWTAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariJWTAuthenticationFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariJWTAuthenticationFilter.java index 195c55a..fca8b29 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariJWTAuthenticationFilter.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariJWTAuthenticationFilter.java @@ -34,7 +34,6 @@ import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.security.authorization.AuthorizationHelper; import org.apache.ambari.server.security.authorization.PermissionHelper; import org.apache.ambari.server.security.authorization.Users; -import org.apache.ambari.server.security.authorization.jwt.AuthenticationJwtUserNotFoundException; import org.apache.ambari.server.security.authorization.jwt.JwtAuthenticationFilter; import org.apache.ambari.server.utils.RequestUtils; import org.springframework.security.core.Authentication; @@ -124,8 +123,8 @@ public class AmbariJWTAuthenticationFilter extends JwtAuthenticationFilter imple protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { if (auditLogger.isEnabled()) { String username = null; - if (authException instanceof AuthenticationJwtUserNotFoundException) { - username = ((AuthenticationJwtUserNotFoundException) authException).getUsername(); + if (authException instanceof UserNotFoundException) { + username = ((UserNotFoundException) authException).getUsername(); } AuditEvent loginFailedAuditEvent = LoginAuditEvent.builder() http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AuthenticationMethodNotAllowedException.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AuthenticationMethodNotAllowedException.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AuthenticationMethodNotAllowedException.java new file mode 100644 index 0000000..4c48dd7 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AuthenticationMethodNotAllowedException.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.security.authentication; + +import org.apache.ambari.server.security.authorization.UserAuthenticationType; +import org.springframework.security.core.AuthenticationException; + +/** + * AuthenticationMethodNotAllowedException is an AuthenticationException implementation to be thrown + * when the specified authentication method is not allowed for the relevant user. + */ +public class AuthenticationMethodNotAllowedException extends AuthenticationException { + private final String username; + private final UserAuthenticationType userAuthenticationType; + + public AuthenticationMethodNotAllowedException(String username, UserAuthenticationType authenticationType) { + this(username, authenticationType, createDefaultMessage(username, authenticationType)); + } + + public AuthenticationMethodNotAllowedException(String username, UserAuthenticationType authenticationType, Throwable cause) { + this(username, authenticationType, createDefaultMessage(username, authenticationType), cause); + } + + public AuthenticationMethodNotAllowedException(String username, UserAuthenticationType authenticationType, String message) { + super(message); + this.username = username; + this.userAuthenticationType = authenticationType; + } + + public AuthenticationMethodNotAllowedException(String username, UserAuthenticationType authenticationType, String message, Throwable cause) { + super(message, cause); + this.username = username; + this.userAuthenticationType = authenticationType; + } + + public String getUsername() { + return username; + } + + public UserAuthenticationType getUserAuthenticationType() { + return userAuthenticationType; + } + + private static String createDefaultMessage(String username, UserAuthenticationType authenticationType) { + return String.format("%s is not authorized to authenticate using %s", + username, + (authenticationType == null) ? "null" : authenticationType.name()); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/f76c87a6/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/UserNotFoundException.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/UserNotFoundException.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/UserNotFoundException.java new file mode 100644 index 0000000..f6c4bcf --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/UserNotFoundException.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.security.authentication; + +import org.springframework.security.core.AuthenticationException; + +/** + * AuthenticationUserNotFoundException is an AuthenticationException implementation to be thrown + * when the user specified in an authentication attempt is not found in the Ambari user database. + */ +public class UserNotFoundException extends AuthenticationException { + private final String username; + + public UserNotFoundException(String username, String message) { + super(message); + this.username = username; + } + + public UserNotFoundException(String username, String message, Throwable throwable) { + super(message, throwable); + this.username = username; + } + + public String getUsername() { + return username; + } +}
