AMBARI-13342. Allow access to callers with valid Knox authorization cookie. (mpapirkovskyy)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8997ce0d Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8997ce0d Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8997ce0d Branch: refs/heads/trunk Commit: 8997ce0d50d91c4cb008d303a07dfd7a949d1788 Parents: edefa48 Author: Myroslav Papirkovskyy <mpapyrkovs...@hortonworks.com> Authored: Mon Nov 9 20:09:12 2015 +0200 Committer: Myroslav Papirkovskyy <mpapyrkovs...@hortonworks.com> Committed: Mon Nov 9 20:09:12 2015 +0200 ---------------------------------------------------------------------- ambari-server/pom.xml | 12 + .../ambari/server/api/AmbariErrorHandler.java | 13 +- .../server/configuration/Configuration.java | 53 +++ .../AmbariManagementControllerImpl.java | 6 +- .../ambari/server/controller/AmbariServer.java | 3 + .../ambari/server/controller/UserResponse.java | 24 +- .../internal/UserPrivilegeResourceProvider.java | 4 + .../internal/UserResourceProvider.java | 4 + .../apache/ambari/server/orm/dao/UserDAO.java | 14 + .../ambari/server/orm/entities/UserEntity.java | 31 +- .../AmbariAuthorizationFilter.java | 8 +- .../server/security/authorization/User.java | 11 +- .../server/security/authorization/UserType.java | 24 ++ .../server/security/authorization/Users.java | 82 ++++- .../authorization/jwt/JwtAuthentication.java | 78 +++++ .../jwt/JwtAuthenticationFilter.java | 329 +++++++++++++++++++ .../jwt/JwtAuthenticationProperties.java | 85 +++++ .../security/encryption/CertificateUtils.java | 51 +++ .../server/upgrade/UpgradeCatalog220.java | 12 + ambari-server/src/main/python/ambari-server.py | 8 +- .../main/python/ambari_server/setupActions.py | 3 +- .../src/main/python/ambari_server/setupSso.py | 121 +++++++ .../main/resources/Ambari-DDL-MySQL-CREATE.sql | 3 +- .../main/resources/Ambari-DDL-Oracle-CREATE.sql | 3 +- .../resources/Ambari-DDL-Postgres-CREATE.sql | 5 +- .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql | 5 +- .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql | 3 +- .../resources/Ambari-DDL-SQLServer-CREATE.sql | 9 +- .../src/main/resources/properties.json | 1 + .../webapp/WEB-INF/spring-security.xml | 5 +- .../server/api/AmbariErrorHandlerTest.java | 4 +- .../jwt/JwtAuthenticationFilterTest.java | 244 ++++++++++++++ 32 files changed, 1214 insertions(+), 44 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/pom.xml ---------------------------------------------------------------------- diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml index 5a7ddc4..ebe5cc2 100644 --- a/ambari-server/pom.xml +++ b/ambari-server/pom.xml @@ -2011,6 +2011,18 @@ <artifactId>ehcache</artifactId> <version>2.10.0</version> </dependency> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + <version>3.9</version> + <scope>compile</scope> + <exclusions> + <exclusion> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </exclusion> + </exclusions> + </dependency> </dependencies> <pluginRepositories> http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/api/AmbariErrorHandler.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/AmbariErrorHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/api/AmbariErrorHandler.java index ee4e56f..d526ac7 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/api/AmbariErrorHandler.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/AmbariErrorHandler.java @@ -21,6 +21,8 @@ package org.apache.ambari.server.api; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.name.Named; +import org.apache.ambari.server.configuration.Configuration; +import org.apache.ambari.server.security.authorization.jwt.JwtAuthenticationProperties; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.AbstractHttpConnection; @@ -35,10 +37,12 @@ import java.util.Map; public class AmbariErrorHandler extends ErrorHandler { private final Gson gson; + private Configuration configuration; @Inject - public AmbariErrorHandler(@Named("prettyGson") Gson prettyGson) { + public AmbariErrorHandler(@Named("prettyGson") Gson prettyGson, Configuration configuration) { this.gson = prettyGson; + this.configuration = configuration; } @Override @@ -57,6 +61,13 @@ public class AmbariErrorHandler extends ErrorHandler { } errorMap.put("message", message); + if (code == HttpServletResponse.SC_FORBIDDEN) { + JwtAuthenticationProperties jwtProperties = configuration.getJwtProperties(); + if (jwtProperties != null) { + errorMap.put("jwtProviderUrl", jwtProperties.getAuthenticationProviderUrl()); + } + } + gson.toJson(errorMap, response.getWriter()); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 e5e2c90..0549c6e 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 @@ -32,8 +32,10 @@ import org.apache.ambari.server.orm.PersistenceType; import org.apache.ambari.server.orm.entities.StageEntity; import org.apache.ambari.server.security.ClientSecurityType; import org.apache.ambari.server.security.authorization.LdapServerProperties; +import org.apache.ambari.server.security.authorization.jwt.JwtAuthenticationProperties; import org.apache.ambari.server.security.encryption.CredentialProvider; import org.apache.ambari.server.state.stack.OsFamily; +import org.apache.ambari.server.security.encryption.CertificateUtils; import org.apache.ambari.server.utils.Parallel; import org.apache.ambari.server.utils.ShellCommandUtil; import org.apache.commons.io.FileUtils; @@ -55,6 +57,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; +import java.security.cert.CertificateException; +import java.security.interfaces.RSAPublicKey; + /** * Ambari configuration. @@ -190,6 +195,14 @@ public class Configuration { public static final String ROLLING_UPGRADE_MIN_STACK_DEFAULT = "HDP-2.2"; public static final String ROLLING_UPGRADE_MAX_STACK_DEFAULT = ""; public static final String ROLLING_UPGRADE_SKIP_PACKAGES_PREFIXES_DEFAULT = ""; + public static final String JWT_AUTH_ENBABLED = "authentication.jwt.enabled"; + public static final String JWT_AUTH_PROVIDER_URL = "authentication.jwt.providerUrl"; + public static final String JWT_PUBLIC_KEY = "authentication.jwt.publicKey"; + public static final String JWT_AUDIENCES = "authentication.jwt.audiences"; + public static final String JWT_COOKIE_NAME = "authentication.jwt.cookieName"; + public static final String JWT_ORIGINAL_URL_QUERY_PARAM = "authentication.jwt.originalUrlParamName"; + public static final String JWT_COOKIE_NAME_DEFAULT = "hadoop-jwt"; + public static final String JWT_ORIGINAL_URL_QUERY_PARAM_DEFAULT = "originalUrl"; public static final String SERVER_JDBC_CONNECTION_POOL = "server.jdbc.connection-pool"; public static final String SERVER_JDBC_CONNECTION_POOL_MIN_SIZE = "server.jdbc.connection-pool.min-size"; @@ -2302,6 +2315,46 @@ public class Configuration { .getProperty(TIMELINE_METRICS_CACHE_USE_CUSTOM_SIZING_ENGINE, "true")); } + public JwtAuthenticationProperties getJwtProperties() { + boolean enableJwt = Boolean.valueOf(properties.getProperty(JWT_AUTH_ENBABLED, "false")); + + if (enableJwt) { + String providerUrl = properties.getProperty(JWT_AUTH_PROVIDER_URL); + if (providerUrl == null) { + LOG.error("JWT authentication provider URL not specified. JWT auth will be disabled.", providerUrl); + return null; + } + String publicKeyPath = properties.getProperty(JWT_PUBLIC_KEY); + if (publicKeyPath == null) { + LOG.error("Public key pem not specified for JWT auth provider {}. JWT auth will be disabled.", providerUrl); + return null; + } + try { + RSAPublicKey publicKey = CertificateUtils.getPublicKeyFromFile(publicKeyPath); + JwtAuthenticationProperties jwtProperties = new JwtAuthenticationProperties(); + jwtProperties.setAuthenticationProviderUrl(providerUrl); + jwtProperties.setPublicKey(publicKey); + + jwtProperties.setCookieName(properties.getProperty(JWT_COOKIE_NAME, JWT_COOKIE_NAME_DEFAULT)); + jwtProperties.setAudiencesString(properties.getProperty(JWT_AUDIENCES)); + jwtProperties.setOriginalUrlQueryParam( + properties.getProperty(JWT_ORIGINAL_URL_QUERY_PARAM, JWT_ORIGINAL_URL_QUERY_PARAM_DEFAULT)); + + return jwtProperties; + + } catch (IOException e) { + LOG.error("Unable to read public certificate file. JWT auth will be disabled.", e); + return null; + } catch (CertificateException e) { + LOG.error("Unable to parse public certificate file. JWT auth will be disabled.", e); + return null; + } + } else { + return null; + } + + } + /** * Gets whether to use experiemental concurrent processing to convert * {@link StageEntity} instances into {@link Stage} instances. The default is http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 49a8d8b..8dd7a04 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 @@ -3202,7 +3202,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle // get them all if (null == r.getUsername()) { for (User u : users.getAllUsers()) { - UserResponse resp = new UserResponse(u.getUserName(), u.isLdapUser(), u.isActive(), u.isAdmin()); + UserResponse resp = new UserResponse(u.getUserName(), u.getUserType(), u.isLdapUser(), u.isActive(), u + .isAdmin()); resp.setGroups(new HashSet<String>(u.getGroups())); responses.add(resp); } @@ -3217,7 +3218,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle + r.getUsername() + "'"); } } else { - UserResponse resp = new UserResponse(u.getUserName(), u.isLdapUser(), u.isActive(), u.isAdmin()); + UserResponse resp = new UserResponse(u.getUserName(), u.getUserType(), u.isLdapUser(), u.isActive(), u + .isAdmin()); resp.setGroups(new HashSet<String>(u.getGroups())); responses.add(resp); } http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 15cfb90..114ecbc 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 @@ -89,6 +89,7 @@ import org.apache.ambari.server.security.authorization.AmbariLdapAuthenticationP import org.apache.ambari.server.security.authorization.AmbariLocalUserDetailsService; import org.apache.ambari.server.security.authorization.Users; import org.apache.ambari.server.security.authorization.internal.AmbariInternalAuthenticationProvider; +import org.apache.ambari.server.security.authorization.jwt.JwtAuthenticationFilter; import org.apache.ambari.server.security.ldap.AmbariLdapDataPopulator; import org.apache.ambari.server.security.unsecured.rest.CertificateDownload; import org.apache.ambari.server.security.unsecured.rest.CertificateSign; @@ -275,6 +276,8 @@ public class AmbariServer { injector.getInstance(AmbariAuthorizationFilter.class)); factory.registerSingleton("ambariInternalAuthenticationProvider", injector.getInstance(AmbariInternalAuthenticationProvider.class)); + factory.registerSingleton("ambariJwtAuthenticationFilter", + injector.getInstance(JwtAuthenticationFilter.class)); //Spring Security xml config depends on this Bean http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 868635c..d481327 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 @@ -17,6 +17,8 @@ */ package org.apache.ambari.server.controller; +import org.apache.ambari.server.security.authorization.UserType; + import java.util.Collections; import java.util.Set; @@ -26,16 +28,26 @@ import java.util.Set; public class UserResponse { private final String userName; + private final UserType userType; 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) { + this.userName = userName; + this.userType = userType; + this.isLdapUser = isLdapUser; + this.isActive = isActive; + this.isAdmin = isAdmin; + } + public UserResponse(String name, boolean isLdapUser, boolean isActive, boolean isAdmin) { this.userName = name; this.isLdapUser = isLdapUser; this.isActive = isActive; this.isAdmin = isAdmin; + this.userType = UserType.LOCAL; } public String getUsername() { @@ -57,17 +69,15 @@ public class UserResponse { UserResponse that = (UserResponse) o; - if (userName != null ? - !userName.equals(that.userName) : that.userName != null) { - return false; - } + if (userName != null ? !userName.equals(that.userName) : that.userName != null) return false; + return userType == that.userType; - return true; } @Override public int hashCode() { int result = userName != null ? userName.hashCode() : 0; + result = 31 * result + (userType != null ? userType.hashCode() : 0); return result; } @@ -85,4 +95,8 @@ public class UserResponse { public boolean isAdmin() { return isAdmin; } + + public UserType getUserType() { + return userType; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 cdc8c8a..0621286 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 @@ -42,6 +42,7 @@ import org.apache.ambari.server.orm.entities.ResourceTypeEntity; import org.apache.ambari.server.orm.entities.UserEntity; import org.apache.ambari.server.orm.entities.ViewEntity; import org.apache.ambari.server.orm.entities.ViewInstanceEntity; +import org.apache.ambari.server.security.authorization.UserType; /** * Resource provider for user privilege resources. @@ -159,6 +160,9 @@ public class UserPrivilegeResourceProvider extends ReadOnlyResourceProvider { userEntity = userDAO.findLdapUserByName(userName); } if (userEntity == null) { + userEntity = userDAO.findUserByNameAndType(userName, UserType.JWT); + } + if (userEntity == null) { throw new SystemException("User " + userName + " was not found"); } http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 35f9db5..b993450 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 @@ -41,6 +41,7 @@ class UserResourceProvider extends AbstractControllerResourceProvider { protected static final String USER_PASSWORD_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "password"); protected static final String USER_OLD_PASSWORD_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "old_password"); protected static final String USER_LDAP_USER_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "ldap_user"); + protected static final String USER_TYPE_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "user_type"); protected static final String USER_ACTIVE_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "active"); protected static final String USER_GROUPS_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "groups"); protected static final String USER_ADMIN_PROPERTY_ID = PropertyHelper.getPropertyId("Users", "admin"); @@ -119,6 +120,9 @@ class UserResourceProvider extends AbstractControllerResourceProvider { setResourceProperty(resource, USER_LDAP_USER_PROPERTY_ID, userResponse.isLdapUser(), requestedIds); + setResourceProperty(resource, USER_TYPE_PROPERTY_ID, + userResponse.getUserType(), requestedIds); + setResourceProperty(resource, USER_ACTIVE_PROPERTY_ID, userResponse.isActive(), requestedIds); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 12f975e..1bd83f8 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 @@ -35,6 +35,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.persist.Transactional; +import org.apache.ambari.server.security.authorization.UserType; @Singleton public class UserDAO { @@ -67,6 +68,19 @@ public class UserDAO { } @RequiresSession + public UserEntity findUserByNameAndType(String userName, UserType userType) { + TypedQuery<UserEntity> query = entityManagerProvider.get().createQuery("SELECT user FROM UserEntity user WHERE " + + "user.userType=:type AND user.userName=:name", UserEntity.class); + 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()); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/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 32dbbf5..5b8360a 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,18 +17,25 @@ */ package org.apache.ambari.server.orm.entities; +import org.apache.ambari.server.security.authorization.UserType; + import javax.persistence.*; import java.util.Date; import java.util.HashSet; import java.util.Set; -@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_name", "ldap_user"})}) +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_name", "user_type"})}) @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.ldapUser=false"), - @NamedQuery(name = "ldapUserByName", query = "SELECT user_entity FROM UserEntity user_entity where lower(user_entity.userName)=:username AND user_entity.ldapUser=true") + @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") }) @TableGenerator(name = "user_id_generator", table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value" @@ -49,6 +56,11 @@ public class UserEntity { @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; @@ -99,9 +111,18 @@ public class UserEntity { this.ldapUser = null; } else { this.ldapUser = ldapUser ? 1 : 0; + this.userType = ldapUser ? UserType.LDAP : UserType.LOCAL; } } + public UserType getUserType() { + return userType; + } + + public void setUserType(UserType userType) { + this.userType = userType; + } + public String getUserPassword() { return userPassword; } @@ -176,6 +197,7 @@ public class UserEntity { 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; @@ -189,6 +211,7 @@ public class UserEntity { 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; http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java index 0abbf45..46b751d 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java @@ -95,13 +95,15 @@ public class AmbariAuthorizationFilter implements Filter { context.setAuthentication(new InternalAuthenticationToken(token)); } else { // for view access, we should redirect to the Ambari login - if(requestURI.matches(VIEWS_CONTEXT_ALL_PATTERN)) { - String queryString = httpRequest.getQueryString(); + if (requestURI.matches(VIEWS_CONTEXT_ALL_PATTERN)) { + String queryString = httpRequest.getQueryString(); String requestedURL = queryString == null ? requestURI : (requestURI + '?' + queryString); - String redirectURL = httpResponse.encodeRedirectURL(LOGIN_REDIRECT_BASE + requestedURL); + String redirectURL = httpResponse.encodeRedirectURL(LOGIN_REDIRECT_BASE + requestedURL); httpResponse.sendRedirect(redirectURL); return; + } else { + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Authentication required"); } } } else { http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java index e72d958..ab48ddd 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java @@ -20,11 +20,13 @@ package org.apache.ambari.server.security.authorization; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.List; import org.apache.ambari.server.orm.entities.MemberEntity; import org.apache.ambari.server.orm.entities.PermissionEntity; import org.apache.ambari.server.orm.entities.PrivilegeEntity; import org.apache.ambari.server.orm.entities.UserEntity; +import org.springframework.security.core.GrantedAuthority; /** * Describes user of web-services @@ -33,15 +35,18 @@ public class User { final int userId; final String userName; final boolean ldapUser; + final UserType userType; final Date createTime; final boolean active; final Collection<String> groups = new ArrayList<String>(); boolean admin = false; + final List<GrantedAuthority> authorities = new ArrayList<>(); public User(UserEntity userEntity) { userId = userEntity.getUserId(); userName = userEntity.getUserName(); createTime = userEntity.getCreateTime(); + userType = userEntity.getUserType(); ldapUser = userEntity.getLdapUser(); active = userEntity.getActive(); for (MemberEntity memberEntity : userEntity.getMemberEntities()) { @@ -67,6 +72,10 @@ public class User { return ldapUser; } + public UserType getUserType() { + return userType; + } + public Date getCreateTime() { return createTime; } @@ -85,6 +94,6 @@ public class User { @Override public String toString() { - return (ldapUser ? "[LDAP]" : "[LOCAL]") + userName; + return "[" + getUserType() + "]" + userName; } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/UserType.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/UserType.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/UserType.java new file mode 100644 index 0000000..aa9f3e0 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/UserType.java @@ -0,0 +1,24 @@ +/** + * 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.authorization; + +public enum UserType { + LOCAL, + LDAP, + JWT +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java index 867cd11..29b9ec3 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java @@ -17,13 +17,7 @@ */ package org.apache.ambari.server.security.authorization; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.persistence.EntityManager; import org.apache.ambari.server.AmbariException; @@ -50,6 +44,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -108,6 +103,11 @@ public class Users { return (null == userEntity) ? null : new User(userEntity); } + public User getUser(String userName, UserType userType) { + UserEntity userEntity = userDAO.findUserByNameAndType(userName, userType); + return (null == userEntity) ? null : new User(userEntity); + } + /** * Modifies password of local user * @throws AmbariException @@ -238,7 +238,6 @@ public class Users { * @param ldapUser is user LDAP * @throws AmbariException if user already exists */ - @Transactional public synchronized void createUser(String userName, String password, Boolean active, Boolean admin, Boolean ldapUser) throws AmbariException { if (getAnyUser(userName) != null) { @@ -275,7 +274,44 @@ public class Users { } } - @Transactional + public synchronized void createUser(String userName, String password, UserType userType, Boolean active, Boolean + admin) throws AmbariException { + if (getUser(userName, userType) != null) { + throw new AmbariException("User " + userName + " already exists"); + } + + PrincipalTypeEntity principalTypeEntity = principalTypeDAO.findById(PrincipalTypeEntity.USER_PRINCIPAL_TYPE); + if (principalTypeEntity == null) { + principalTypeEntity = new PrincipalTypeEntity(); + principalTypeEntity.setId(PrincipalTypeEntity.USER_PRINCIPAL_TYPE); + principalTypeEntity.setName(PrincipalTypeEntity.USER_PRINCIPAL_TYPE_NAME); + principalTypeDAO.create(principalTypeEntity); + } + PrincipalEntity principalEntity = new PrincipalEntity(); + principalEntity.setPrincipalType(principalTypeEntity); + principalDAO.create(principalEntity); + + UserEntity userEntity = new UserEntity(); + userEntity.setUserName(userName); + if (userType == null || userType == UserType.LOCAL) { + //passwords should be stored for local users only + userEntity.setUserPassword(passwordEncoder.encode(password)); + } + userEntity.setPrincipal(principalEntity); + if (active != null) { + userEntity.setActive(active); + } + if (userType != null) { + userEntity.setUserType(userType); + } + + userDAO.create(userEntity); + + if (admin != null && admin) { + grantAdminPrivilege(userEntity.getUserId()); + } + } + public synchronized void removeUser(User user) throws AmbariException { UserEntity userEntity = userDAO.findByPK(user.getUserId()); if (userEntity != null) { @@ -654,4 +690,32 @@ public class Users { entityManagerProvider.get().getEntityManagerFactory().getCache().evictAll(); } + public Collection<AmbariGrantedAuthority> getUserAuthorities(String userName, UserType userType) { + UserEntity userEntity = userDAO.findUserByNameAndType(userName, userType); + if (userEntity == null) { + return Collections.emptyList(); + } + + // get all of the privileges for the user + List<PrincipalEntity> principalEntities = new LinkedList<PrincipalEntity>(); + + principalEntities.add(userEntity.getPrincipal()); + + List<MemberEntity> memberEntities = memberDAO.findAllMembersByUser(userEntity); + + for (MemberEntity memberEntity : memberEntities) { + principalEntities.add(memberEntity.getGroup().getPrincipal()); + } + + List<PrivilegeEntity> privilegeEntities = privilegeDAO.findAllByPrincipal(principalEntities); + + Set<AmbariGrantedAuthority> authorities = new HashSet<>(privilegeEntities.size()); + + for (PrivilegeEntity privilegeEntity : privilegeEntities) { + authorities.add(new AmbariGrantedAuthority(privilegeEntity)); + } + + return authorities; + } + } http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthentication.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthentication.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthentication.java new file mode 100644 index 0000000..53826c0 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthentication.java @@ -0,0 +1,78 @@ +/* + * 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.authorization.jwt; + +import com.nimbusds.jwt.SignedJWT; +import org.apache.ambari.server.security.authorization.AmbariGrantedAuthority; +import org.apache.ambari.server.security.authorization.User; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + * Internal token which describes JWT authentication + */ +public class JwtAuthentication implements Authentication { + + private SignedJWT token; + private User user; + private Collection<AmbariGrantedAuthority> userAuthorities; + private boolean authenticated = false; + + public JwtAuthentication(SignedJWT token, User user, Collection<AmbariGrantedAuthority> userAuthorities) { + this.token = token; + this.user = user; + this.userAuthorities = userAuthorities; + } + + @Override + public Collection<? extends AmbariGrantedAuthority> getAuthorities() { + return userAuthorities; + } + + @Override + public SignedJWT getCredentials() { + return token; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public User getPrincipal() { + return user; + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(boolean authenticated) throws IllegalArgumentException { + this.authenticated = authenticated; + } + + @Override + public String getName() { + return user.getUserName(); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..d061c69 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,329 @@ +/* + * 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.authorization.jwt; + +import com.google.inject.Inject; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSObject; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jwt.SignedJWT; +import org.apache.ambari.server.configuration.Configuration; +import org.apache.ambari.server.security.authorization.AmbariGrantedAuthority; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.*; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * Filter is used to validate JWT token and authenticate user. + * It is also responsive for creating user in local Ambari database for further management + */ +public class JwtAuthenticationFilter implements Filter { + Logger LOG = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + private final JwtAuthenticationProperties jwtProperties; + + private String originalUrlQueryParam = "originalUrl"; + private String authenticationProviderUrl = null; + private RSAPublicKey publicKey = null; + private List<String> audiences = null; + private String cookieName = "hadoop-jwt"; + + private boolean ignoreFailure = true; + private AuthenticationEntryPoint entryPoint; + private Users users; + + @Inject + public JwtAuthenticationFilter(Configuration configuration, AuthenticationEntryPoint entryPoint, Users users) { + this.entryPoint = entryPoint; + this.users = users; + jwtProperties = configuration.getJwtProperties(); + loadJwtProperties(); + } + + public JwtAuthenticationFilter(JwtAuthenticationProperties jwtProperties, AuthenticationEntryPoint entryPoint, + Users users) { + this.jwtProperties = jwtProperties; + this.entryPoint = entryPoint; + this.users = users; + loadJwtProperties(); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + + if (jwtProperties != null && isAuthenticationRequired()) { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; + + String serializedJWT = getJWTFromCookie(httpServletRequest); + if (serializedJWT != null) { + SignedJWT jwtToken = null; + try { + jwtToken = SignedJWT.parse(serializedJWT); + boolean valid = validateToken(jwtToken); + + if (valid) { + String userName = jwtToken.getJWTClaimsSet().getSubject(); + User user = users.getUser(userName, UserType.JWT); + if (user == null) { + // create user in local database on first login, usually we cannot fetch all users + // from external authentication provider (as we do during ldap-sync process) + users.createUser(userName, null, UserType.JWT, true, false); + user = users.getUser(userName, UserType.JWT); + } + + Collection<AmbariGrantedAuthority> userAuthorities = + users.getUserAuthorities(user.getUserName(), user.getUserType()); + + JwtAuthentication token = new JwtAuthentication(jwtToken, user, userAuthorities); + token.setAuthenticated(true); + + SecurityContextHolder.getContext().setAuthentication(token); + + + } else { + LOG.warn("JWT authentication failed"); + if (ignoreFailure) { + filterChain.doFilter(servletRequest, servletResponse); + } else { + //used to indicate authentication failure, not used here as we have more than one filter + entryPoint.commence(httpServletRequest, httpServletResponse, new BadCredentialsException("Invalid JWT " + + "token")); + } + } + + + + } catch (ParseException e) { + LOG.warn("Unable to parse the JWT token", e); + } + } + } + + filterChain.doFilter(servletRequest, servletResponse); + } + + private void loadJwtProperties() { + if (jwtProperties != null) { + authenticationProviderUrl = jwtProperties.getAuthenticationProviderUrl(); + publicKey = jwtProperties.getPublicKey(); + audiences = jwtProperties.getAudiences(); + cookieName = jwtProperties.getCookieName(); + originalUrlQueryParam = jwtProperties.getOriginalUrlQueryParam(); + } + } + + /** + * Do not try to validate JWT if user already authenticated via other provider + * @return true, if JWT validation required + */ + private boolean isAuthenticationRequired() { + Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); + return !(existingAuth != null && existingAuth.isAuthenticated()) || existingAuth instanceof JwtAuthentication; + } + + /** + * Encapsulate the acquisition of the JWT token from HTTP cookies within the + * request. + * + * @param req servlet request to get the JWT token from + * @return serialized JWT token + */ + protected String getJWTFromCookie(HttpServletRequest req) { + String serializedJWT = null; + Cookie[] cookies = req.getCookies(); + String userName = null; + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookieName.equals(cookie.getName())) { + LOG.info(cookieName + + " cookie has been found and is being processed"); + serializedJWT = cookie.getValue(); + break; + } + } + } + return serializedJWT; + } + /** + * Create the URL to be used for authentication of the user in the absence of + * a JWT token within the incoming request. + * + * @param request for getting the original request URL + * @return url to use as login url for redirect + */ + protected String constructLoginURL(HttpServletRequest request) { + String delimiter = "?"; + if (authenticationProviderUrl.contains("?")) { + delimiter = "&"; + } + String loginURL = authenticationProviderUrl + delimiter + + originalUrlQueryParam + "=" + + request.getRequestURL().toString(); + return loginURL; + } + + /** + * This method provides a single method for validating the JWT for use in + * request processing. It provides for the override of specific aspects of + * this implementation through submethods used within but also allows for the + * override of the entire token validation algorithm. + * + * @param jwtToken the token to validate + * @return true if valid + */ + protected boolean validateToken(SignedJWT jwtToken) { + boolean sigValid = validateSignature(jwtToken); + if (!sigValid) { + LOG.warn("Signature could not be verified"); + } + boolean audValid = validateAudiences(jwtToken); + if (!audValid) { + LOG.warn("Audience validation failed."); + } + boolean expValid = validateExpiration(jwtToken); + if (!expValid) { + LOG.info("Expiration validation failed."); + } + + return sigValid && audValid && expValid; + } + + /** + * Verify the signature of the JWT token in this method. This method depends + * on the public key that was established during init based upon the + * provisioned public key. Override this method in subclasses in order to + * customize the signature verification behavior. + * + * @param jwtToken the token that contains the signature to be validated + * @return valid true if signature verifies successfully; false otherwise + */ + protected boolean validateSignature(SignedJWT jwtToken) { + boolean valid = false; + if (JWSObject.State.SIGNED == jwtToken.getState()) { + LOG.debug("JWT token is in a SIGNED state"); + if (jwtToken.getSignature() != null) { + LOG.debug("JWT token signature is not null"); + try { + JWSVerifier verifier = new RSASSAVerifier(publicKey); + if (jwtToken.verify(verifier)) { + valid = true; + LOG.debug("JWT token has been successfully verified"); + } else { + LOG.warn("JWT signature verification failed."); + } + } catch (JOSEException je) { + LOG.warn("Error while validating signature", je); + } + } + } + return valid; + } + + /** + * Validate whether any of the accepted audience claims is present in the + * issued token claims list for audience. Override this method in subclasses + * in order to customize the audience validation behavior. + * + * @param jwtToken + * the JWT token where the allowed audiences will be found + * @return true if an expected audience is present, otherwise false + */ + protected boolean validateAudiences(SignedJWT jwtToken) { + boolean valid = false; + try { + List<String> tokenAudienceList = jwtToken.getJWTClaimsSet() + .getAudience(); + // if there were no expected audiences configured then just + // consider any audience acceptable + if (audiences == null) { + valid = true; + } else { + // if any of the configured audiences is found then consider it + // acceptable + boolean found = false; + for (String aud : tokenAudienceList) { + if (audiences.contains(aud)) { + LOG.debug("JWT token audience has been successfully validated"); + valid = true; + break; + } + } + if (!valid) { + LOG.warn("JWT audience validation failed."); + } + } + } catch (ParseException pe) { + LOG.warn("Unable to parse the JWT token.", pe); + } + return valid; + } + + /** + * Validate that the expiration time of the JWT token has not been violated. + * If it has then throw an AuthenticationException. Override this method in + * subclasses in order to customize the expiration validation behavior. + * + * @param jwtToken the token that contains the expiration date to validate + * @return valid true if the token has not expired; false otherwise + */ + protected boolean validateExpiration(SignedJWT jwtToken) { + boolean valid = false; + try { + Date expires = jwtToken.getJWTClaimsSet().getExpirationTime(); + if (expires != null && new Date().before(expires)) { + LOG.debug("JWT token expiration date has been " + + "successfully validated"); + valid = true; + } else { + LOG.warn("JWT expiration date validation failed."); + } + } catch (ParseException pe) { + LOG.warn("JWT expiration date validation failed.", pe); + } + return valid; + } + + @Override + public void destroy() { + + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationProperties.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationProperties.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationProperties.java new file mode 100644 index 0000000..d237a95 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/jwt/JwtAuthenticationProperties.java @@ -0,0 +1,85 @@ +/* + * 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.authorization.jwt; + +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Class describes parameters of external JWT authentication provider + */ +public class JwtAuthenticationProperties { + private String authenticationProviderUrl = null; + private RSAPublicKey publicKey = null; + private List<String> audiences = null; + private String cookieName = "hadoop-jwt"; + private String originalUrlQueryParam = null; + + public String getAuthenticationProviderUrl() { + return authenticationProviderUrl; + } + + public void setAuthenticationProviderUrl(String authenticationProviderUrl) { + this.authenticationProviderUrl = authenticationProviderUrl; + } + + public RSAPublicKey getPublicKey() { + return publicKey; + } + + public void setPublicKey(RSAPublicKey publicKey) { + this.publicKey = publicKey; + } + + public List<String> getAudiences() { + return audiences; + } + + public void setAudiences(List<String> audiences) { + this.audiences = audiences; + } + + public void setAudiencesString(String audiencesString) { + if (audiencesString != null) { + // parse into the list + String[] audArray = audiencesString.split(","); + audiences = new ArrayList<String>(); + Collections.addAll(audiences, audArray); + } else { + audiences = null; + } + } + + public String getCookieName() { + return cookieName; + } + + public void setCookieName(String cookieName) { + this.cookieName = cookieName; + } + + public String getOriginalUrlQueryParam() { + return originalUrlQueryParam; + } + + public void setOriginalUrlQueryParam(String originalUrlQueryParam) { + this.originalUrlQueryParam = originalUrlQueryParam; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CertificateUtils.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CertificateUtils.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CertificateUtils.java new file mode 100644 index 0000000..16911fa --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CertificateUtils.java @@ -0,0 +1,51 @@ +/* + * 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.encryption; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; + +public class CertificateUtils { + private static final Logger LOG = LoggerFactory.getLogger(CertificateUtils.class); + + public static RSAPublicKey getPublicKeyFromFile(String filePath) throws IOException, CertificateException { + String pemString = FileUtils.readFileToString(new File(filePath)); + return getPublicKeyFromString(pemString); + } + + public static RSAPublicKey getPublicKeyFromString(String certificateString) + throws CertificateException, UnsupportedEncodingException { + + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + ByteArrayInputStream is = new ByteArrayInputStream( + certificateString.getBytes("UTF8")); + + X509Certificate cer = (X509Certificate) fact.generateCertificate(is); + return (RSAPublicKey)cer.getPublicKey(); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog220.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog220.java b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog220.java index 804d97b..8e0aebb 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog220.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog220.java @@ -20,6 +20,7 @@ package org.apache.ambari.server.upgrade; import java.sql.SQLException; +import com.google.inject.Provider; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.orm.DBAccessor.DBColumnInfo; import org.apache.ambari.server.orm.dao.DaoUtils; @@ -29,13 +30,18 @@ import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Injector; +import javax.persistence.EntityManager; + /** * Upgrade catalog for version 2.2.0. */ public class UpgradeCatalog220 extends AbstractUpgradeCatalog { private static final String HOST_ROLE_COMMAND_TABLE = "host_role_command"; + private static final String USERS_TABLE = "users"; + private static final String HOST_ID_COL = "host_id"; + private static final String USER_TYPE_COL = "user_type"; @Inject DaoUtils daoUtils; @@ -85,6 +91,12 @@ public class UpgradeCatalog220 extends AbstractUpgradeCatalog { protected void executeDDLUpdates() throws AmbariException, SQLException { dbAccessor.alterColumn(HOST_ROLE_COMMAND_TABLE, new DBColumnInfo(HOST_ID_COL, Long.class, null, null, true)); + dbAccessor.addColumn(USERS_TABLE, new DBColumnInfo(USER_TYPE_COL, String.class, null, "LOCAL", true)); + + dbAccessor.executeQuery("UPDATE users SET user_type='LDAP' WHERE ldap_user=1"); + + dbAccessor.addUniqueConstraint(USERS_TABLE, "UNQ_users_0", "user_name", "user_type"); + } @Override http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/python/ambari-server.py ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/python/ambari-server.py b/ambari-server/src/main/python/ambari-server.py index 3ea608c..a0f12e0 100755 --- a/ambari-server/src/main/python/ambari-server.py +++ b/ambari-server/src/main/python/ambari-server.py @@ -36,12 +36,14 @@ from ambari_server.serverUtils import is_server_runing, refresh_stack_hash from ambari_server.serverSetup import reset, setup, setup_jce_policy from ambari_server.serverUpgrade import upgrade, upgrade_stack, set_current from ambari_server.setupHttps import setup_https, setup_truststore +from ambari_server.setupSso import setup_sso from ambari_server.hostUpdate import update_host_names from ambari_server.enableStack import enable_stack_version from ambari_server.setupActions import BACKUP_ACTION, LDAP_SETUP_ACTION, LDAP_SYNC_ACTION, PSTART_ACTION, \ REFRESH_STACK_HASH_ACTION, RESET_ACTION, RESTORE_ACTION, UPDATE_HOST_NAMES_ACTION, SETUP_ACTION, SETUP_SECURITY_ACTION, \ - START_ACTION, STATUS_ACTION, STOP_ACTION, UPGRADE_ACTION, UPGRADE_STACK_ACTION, SETUP_JCE_ACTION, SET_CURRENT_ACTION, ENABLE_STACK_ACTION + START_ACTION, STATUS_ACTION, STOP_ACTION, UPGRADE_ACTION, UPGRADE_STACK_ACTION, SETUP_JCE_ACTION, \ + SET_CURRENT_ACTION, ENABLE_STACK_ACTION, SETUP_SSO_ACTION from ambari_server.setupSecurity import setup_ldap, sync_ldap, setup_master_key, setup_ambari_krb5_jaas from ambari_server.userInput import get_validated_string_input @@ -507,6 +509,7 @@ def create_user_action_map(args, options): LDAP_SETUP_ACTION: UserAction(setup_ldap), SETUP_SECURITY_ACTION: UserActionRestart(setup_security, options), REFRESH_STACK_HASH_ACTION: UserAction(refresh_stack_hash_action), + SETUP_SSO_ACTION: UserActionRestart(setup_sso, options) } return action_map @@ -529,7 +532,8 @@ def create_user_action_map(args, options): BACKUP_ACTION: UserActionPossibleArgs(backup, [1, 2], args), RESTORE_ACTION: UserActionPossibleArgs(restore, [1, 2], args), UPDATE_HOST_NAMES_ACTION: UserActionPossibleArgs(update_host_names, [2], args, options), - ENABLE_STACK_ACTION: UserAction(enable_stack, options, args) + ENABLE_STACK_ACTION: UserAction(enable_stack, options, args), + SETUP_SSO_ACTION: UserActionRestart(setup_sso, options) } return action_map http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/python/ambari_server/setupActions.py ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/python/ambari_server/setupActions.py b/ambari-server/src/main/python/ambari_server/setupActions.py index ec2d788..5989020 100644 --- a/ambari-server/src/main/python/ambari_server/setupActions.py +++ b/ambari-server/src/main/python/ambari_server/setupActions.py @@ -30,6 +30,7 @@ REFRESH_STACK_HASH_ACTION = "refresh-stack-hash" STATUS_ACTION = "status" SETUP_HTTPS_ACTION = "setup-https" LDAP_SETUP_ACTION = "setup-ldap" +SETUP_SSO_ACTION = "setup-sso" LDAP_SYNC_ACTION = "sync-ldap" SET_CURRENT_ACTION = "set-current" SETUP_GANGLIA_HTTPS_ACTION = "setup-ganglia-https" @@ -39,4 +40,4 @@ UPDATE_HOST_NAMES_ACTION = "update-host-names" BACKUP_ACTION = "backup" RESTORE_ACTION = "restore" SETUP_JCE_ACTION = "setup-jce" -ENABLE_STACK_ACTION = "enable-stack" +ENABLE_STACK_ACTION = "enable-stack" \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/python/ambari_server/setupSso.py ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/python/ambari_server/setupSso.py b/ambari-server/src/main/python/ambari_server/setupSso.py new file mode 100644 index 0000000..a7c9108 --- /dev/null +++ b/ambari-server/src/main/python/ambari_server/setupSso.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +''' +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. +''' + +from ambari_commons.os_utils import is_root, run_os_command, copy_file, set_file_permissions, remove_file +from ambari_commons.exceptions import FatalException, NonFatalException +from ambari_commons.logging_utils import get_silent, print_warning_msg, print_error_msg +from ambari_server.userInput import get_validated_string_input, get_prompt_default, read_password, get_YN_input + +from ambari_server.serverConfiguration import get_ambari_properties, get_value_from_properties, update_properties, \ + store_password_file + +JWT_AUTH_ENBABLED = "authentication.jwt.enabled" +JWT_AUTH_PROVIDER_URL = "authentication.jwt.providerUrl" +JWT_PUBLIC_KEY = "authentication.jwt.publicKey" +JWT_AUDIENCES = "authentication.jwt.audiences" +JWT_COOKIE_NAME = "authentication.jwt.cookieName" +JWT_ORIGINAL_URL_QUERY_PARAM = "authentication.jwt.originalUrlParamName" + +JWT_COOKIE_NAME_DEFAULT = "hadoop-jwt" +JWT_ORIGINAL_URL_QUERY_PARAM_DEFAULT = "originalUrl" + +REGEX_ANYTHING = ".*" + +JWT_PUBLIC_KEY_FILENAME = "jwt-cert.crt" +JWT_PUBLIC_KEY_HEADER = "-----BEGIN CERTIFICATE-----\n" +JWT_PUBLIC_KEY_FOOTER = "\n-----END CERTIFICATE-----" + + + +def setup_sso(args): + if not is_root(): + err = 'ambari-server setup-sso should be run with ' \ + 'root-level privileges' + raise FatalException(4, err) + if not get_silent(): + properties = get_ambari_properties() + + must_setup_params = False + + sso_enabled = properties.get_property(JWT_AUTH_ENBABLED).lower() in ['true'] + + if sso_enabled: + if get_YN_input("Do you want to disable SSO authentication [y/n] (n)?", False): + properties.process_pair(JWT_AUTH_ENBABLED, "false") + else: + if get_YN_input("Do you want to configure SSO authentication [y/n] (y)?", True): + must_setup_params = True + else: + return False + + if must_setup_params: + provider_url = properties.get_property(JWT_AUTH_PROVIDER_URL) + provider_url = get_validated_string_input("Provider URL [URL] ({}):".format(provider_url), + provider_url, + REGEX_ANYTHING, + "Invalid provider URL", + False) + properties.process_pair(JWT_AUTH_PROVIDER_URL, provider_url) + + cert_string = properties.get_property(JWT_PUBLIC_KEY) + cert_string = get_validated_string_input("Public Certificate [BASE64] ({}):".format('stored' if cert_string else 'empty'), + cert_string, + REGEX_ANYTHING, + "Invalid public certificae string", + False) + + if get_YN_input("Do you want to configure advanced properties [y/n] (n) ?", False): + cookie_name = get_value_from_properties(properties, JWT_COOKIE_NAME, JWT_COOKIE_NAME_DEFAULT) + cookie_name = get_validated_string_input("JWT Cookie name ({}):".format(cookie_name), + cookie_name, + REGEX_ANYTHING, + "Invalid cookie name", + False) + properties.process_pair(JWT_COOKIE_NAME, cookie_name) + + audiences = properties.get_property(JWT_AUDIENCES) + audiences = get_validated_string_input("JWT audiences list (comma-separated), empty for any ({}):".format(audiences), + audiences, + REGEX_ANYTHING, + "Invalid value", + False) + properties.process_pair(JWT_AUDIENCES, audiences) + + # TODO not required for now as we support Knox only + # orig_query_param = get_value_from_properties(JWT_ORIGINAL_URL_QUERY_PARAM, JWT_ORIGINAL_URL_QUERY_PARAM_DEFAULT) + # orig_query_param = get_validated_string_input("Original URL query parameter name ({}):".format(orig_query_param), + # orig_query_param, + # REGEX_ANYTHING, + # "Invalid value", + # False) + # properties.process_pair(JWT_ORIGINAL_URL_QUERY_PARAM, orig_query_param) + + full_cert = JWT_PUBLIC_KEY_HEADER + cert_string + JWT_PUBLIC_KEY_FOOTER + cert_path = store_password_file(full_cert, JWT_PUBLIC_KEY_FILENAME) + properties.process_pair(JWT_PUBLIC_KEY, cert_path) + + update_properties(properties) + + pass + else: + warning = "setup-sso is not enabled in silent mode." + raise NonFatalException(warning) + + pass http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql index 62d8054..df1d52a 100644 --- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql +++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql @@ -196,6 +196,7 @@ CREATE TABLE users ( principal_id BIGINT NOT NULL, create_time TIMESTAMP DEFAULT NOW(), ldap_user INTEGER NOT NULL DEFAULT 0, + user_type VARCHAR(255) NOT NULL DEFAULT 'LOCAL', user_name VARCHAR(255) NOT NULL, user_password VARCHAR(255), active INTEGER NOT NULL DEFAULT 1, @@ -648,7 +649,7 @@ CREATE TABLE topology_logical_task ( ); -- altering tables by creating unique constraints---------- -ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user); +ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, user_type); ALTER TABLE groups ADD CONSTRAINT UNQ_groups_0 UNIQUE (group_name, ldap_group); ALTER TABLE members ADD CONSTRAINT UNQ_members_0 UNIQUE (group_id, user_id); ALTER TABLE clusterconfig ADD CONSTRAINT UQ_config_type_tag UNIQUE (cluster_id, type_name, version_tag); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql index fe024ce..45da04c 100644 --- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql +++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql @@ -187,6 +187,7 @@ CREATE TABLE users ( create_time TIMESTAMP NULL, ldap_user NUMBER(10) DEFAULT 0, user_name VARCHAR2(255) NULL, + user_type VARCHAR(255) DEFAULT 'LOCAL', user_password VARCHAR2(255) NULL, active INTEGER DEFAULT 1 NOT NULL, active_widget_layouts VARCHAR2(1024) DEFAULT NULL, @@ -637,7 +638,7 @@ CREATE TABLE topology_logical_task ( ); --------altering tables by creating unique constraints---------- -ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user); +ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, user_type); ALTER TABLE groups ADD CONSTRAINT UNQ_groups_0 UNIQUE (group_name, ldap_group); ALTER TABLE members ADD CONSTRAINT UNQ_members_0 UNIQUE (group_id, user_id); ALTER TABLE clusterconfig ADD CONSTRAINT UQ_config_type_tag UNIQUE (cluster_id, type_name, version_tag); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql index 3ae65ee..2ecbe3c 100644 --- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql +++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql @@ -195,12 +195,12 @@ CREATE TABLE users ( principal_id BIGINT NOT NULL, ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, + user_type VARCHAR(255) NOT NULL DEFAULT 'LOCAL', create_time TIMESTAMP DEFAULT NOW(), user_password VARCHAR(255), active INTEGER NOT NULL DEFAULT 1, active_widget_layouts VARCHAR(1024) DEFAULT NULL, - PRIMARY KEY (user_id), - UNIQUE (ldap_user, user_name)); + PRIMARY KEY (user_id)); CREATE TABLE groups ( group_id INTEGER, @@ -642,6 +642,7 @@ CREATE TABLE topology_logical_task ( ); --------altering tables by creating unique constraints---------- +ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, user_type); ALTER TABLE clusterconfig ADD CONSTRAINT UQ_config_type_tag UNIQUE (cluster_id, type_name, version_tag); ALTER TABLE clusterconfig ADD CONSTRAINT UQ_config_type_version UNIQUE (cluster_id, type_name, version); ALTER TABLE hosts ADD CONSTRAINT UQ_hosts_host_name UNIQUE (host_name); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql index c014443..0e6cd6f 100644 --- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql +++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql @@ -222,12 +222,12 @@ CREATE TABLE ambari.users ( principal_id BIGINT NOT NULL, ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, + user_type VARCHAR(255) NOT NULL DEFAULT 'LOCAL', create_time TIMESTAMP DEFAULT NOW(), user_password VARCHAR(255), active INTEGER NOT NULL DEFAULT 1, active_widget_layouts VARCHAR(1024) DEFAULT NULL, - PRIMARY KEY (user_id), - UNIQUE (ldap_user, user_name)); + PRIMARY KEY (user_id)); GRANT ALL PRIVILEGES ON TABLE ambari.users TO :username; CREATE TABLE ambari.groups ( @@ -721,6 +721,7 @@ CREATE TABLE ambari.topology_logical_task ( GRANT ALL PRIVILEGES ON TABLE ambari.topology_logical_task TO :username; --------altering tables by creating unique constraints---------- +ALTER TABLE ambari.users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, user_type); ALTER TABLE ambari.clusterconfig ADD CONSTRAINT UQ_config_type_tag UNIQUE (cluster_id, type_name, version_tag); ALTER TABLE ambari.clusterconfig ADD CONSTRAINT UQ_config_type_version UNIQUE (cluster_id, type_name, version); ALTER TABLE ambari.hosts ADD CONSTRAINT UQ_hosts_host_name UNIQUE (host_name); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql index bba17a5..e53c510 100644 --- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql +++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql @@ -186,6 +186,7 @@ CREATE TABLE users ( create_time TIMESTAMP DEFAULT NOW(), ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, + user_type VARCHAR(255) NOT NULL DEFAULT 'LOCAL', user_password VARCHAR(255), active INTEGER NOT NULL DEFAULT 1, active_widget_layouts VARCHAR(1024) DEFAULT NULL, @@ -638,7 +639,7 @@ CREATE TABLE topology_logical_task ( ); -- altering tables by creating unique constraints---------- -ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user); +ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, user_type); ALTER TABLE groups ADD CONSTRAINT UNQ_groups_0 UNIQUE (group_name, ldap_group); ALTER TABLE members ADD CONSTRAINT UNQ_members_0 UNIQUE (group_id, user_id); ALTER TABLE clusterconfig ADD CONSTRAINT UQ_config_type_tag UNIQUE (cluster_id, type_name, version_tag); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql index 60938c3..1839298 100644 --- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql +++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql @@ -207,15 +207,12 @@ CREATE TABLE users ( principal_id BIGINT NOT NULL, ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, + user_type VARCHAR(255) NOT NULL DEFAULT 'LOCAL', create_time DATETIME DEFAULT GETDATE(), user_password VARCHAR(255), active INTEGER NOT NULL DEFAULT 1, active_widget_layouts VARCHAR(1024) DEFAULT NULL, - PRIMARY KEY CLUSTERED (user_id), - UNIQUE ( - ldap_user, - user_name - ) + PRIMARY KEY CLUSTERED (user_id) ); CREATE TABLE groups ( @@ -754,7 +751,7 @@ CREATE TABLE topology_logical_task ( -- altering tables by creating unique constraints---------- --------altering tables to add constraints---------- -ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user); +ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, user_type); ALTER TABLE groups ADD CONSTRAINT UNQ_groups_0 UNIQUE (group_name, ldap_group); ALTER TABLE members ADD CONSTRAINT UNQ_members_0 UNIQUE (group_id, user_id); ALTER TABLE clusterconfig ADD CONSTRAINT UQ_config_type_tag UNIQUE (cluster_id, type_name, version_tag); http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/properties.json ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/properties.json b/ambari-server/src/main/resources/properties.json index 727bdc7..4052ad2 100644 --- a/ambari-server/src/main/resources/properties.json +++ b/ambari-server/src/main/resources/properties.json @@ -165,6 +165,7 @@ "Users/password", "Users/old_password", "Users/ldap_user", + "Users/user_type", "Users/active", "Users/groups", "Users/admin", http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml b/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml index 097e233..8b5bd97 100644 --- a/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml +++ b/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml @@ -24,9 +24,10 @@ <http use-expressions="true" disable-url-rewriting="true" entry-point-ref="ambariEntryPoint"> - <http-basic entry-point-ref="ambariEntryPoint"/> + <http-basic/> <intercept-url pattern="/**" access="isAuthenticated()"/> - <custom-filter ref="ambariAuthorizationFilter" after="BASIC_AUTH_FILTER"/> + <custom-filter ref="ambariJwtAuthenticationFilter" after="BASIC_AUTH_FILTER" /> + <custom-filter ref="ambariAuthorizationFilter" before="FILTER_SECURITY_INTERCEPTOR"/> </http> <!--<ldap-server id="ldapServer" root="dc=ambari,dc=apache,dc=org"/>--> http://git-wip-us.apache.org/repos/asf/ambari/blob/8997ce0d/ambari-server/src/test/java/org/apache/ambari/server/api/AmbariErrorHandlerTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/AmbariErrorHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/AmbariErrorHandlerTest.java index fb41f6e..8b4f99c 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/api/AmbariErrorHandlerTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/api/AmbariErrorHandlerTest.java @@ -21,6 +21,7 @@ package org.apache.ambari.server.api; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.sun.jersey.api.client.*; +import org.apache.ambari.server.configuration.Configuration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -51,13 +52,14 @@ public class AmbariErrorHandlerTest { @Test public void testErrorWithJetty() throws Exception { Server server = new Server(0); + Configuration configuration = new Configuration(); ServletContextHandler root = new ServletContextHandler(server, "/", ServletContextHandler.SECURITY | ServletContextHandler.SESSIONS); root.addServlet(HelloServlet.class, "/hello"); root.addServlet(DefaultServlet.class, "/"); - root.setErrorHandler(new AmbariErrorHandler(gson)); + root.setErrorHandler(new AmbariErrorHandler(gson, configuration)); server.start();