Ver código fonte

AMBARI-12613. Allow for granular authorization inorder to update User resource properties via Ambari's REST API (rlevas)

Robert Levas 10 anos atrás
pai
commit
bf0b6cb7fb

+ 50 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java

@@ -88,9 +88,13 @@ import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
 import org.apache.ambari.server.orm.entities.ClusterVersionEntity;
 import org.apache.ambari.server.orm.entities.OperatingSystemEntity;
+import org.apache.ambari.server.orm.entities.PermissionEntity;
+import org.apache.ambari.server.orm.entities.PrivilegeEntity;
 import org.apache.ambari.server.orm.entities.RepositoryEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
 import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
+import org.apache.ambari.server.security.SecurityHelper;
+import org.apache.ambari.server.security.authorization.AmbariGrantedAuthority;
 import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.security.authorization.Group;
 import org.apache.ambari.server.security.authorization.User;
@@ -151,6 +155,7 @@ import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
+import org.springframework.security.core.GrantedAuthority;
 
 @Singleton
 public class AmbariManagementControllerImpl implements AmbariManagementController {
@@ -217,6 +222,9 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
    */
   private KerberosHelper kerberosHelper;
 
+  @Inject
+  private SecurityHelper securityHelper;
+
   final private String masterHostname;
   final private Integer masterPort;
   final private String masterProtocol;
@@ -2321,6 +2329,15 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     return serviceName;
   }
 
+  /**
+   * 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 {
     for (UserRequest request : requests) {
@@ -2335,10 +2352,17 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       }
 
       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
+        verifyAuthorization();
         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
+        verifyAuthorization();
         if (request.isAdmin()) {
           users.grantAdminPrivilege(u.getUserId());
         } else {
@@ -3760,4 +3784,30 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       ldapSyncInProgress = false;
     }
   }
+
+  /**
+   * Determine whether or not the authenticated user has administrator privileges
+   *
+   * @throws IllegalArgumentException if the authenticated user does not have administrator privileges.
+   */
+  protected void verifyAuthorization() throws AmbariException {
+    boolean isAuthorized = false;
+
+    for (GrantedAuthority grantedAuthority : securityHelper.getCurrentAuthorities()) {
+      if (grantedAuthority instanceof AmbariGrantedAuthority) {
+        AmbariGrantedAuthority authority = (AmbariGrantedAuthority) grantedAuthority;
+        PrivilegeEntity privilegeEntity = authority.getPrivilegeEntity();
+        Integer permissionId = privilegeEntity.getPermission().getId();
+
+        if (permissionId.equals(PermissionEntity.AMBARI_ADMIN_PERMISSION)) {
+          isAuthorized = true;
+          break;
+        }
+      }
+    }
+
+    if (!isAuthorized) {
+      throw new IllegalArgumentException("You do not have authorization to update the requested resource property.");
+    }
+  }
 }

+ 354 - 9
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UserResourceProviderTest.java

@@ -18,9 +18,19 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.apache.ambari.server.actionmanager.ActionDBAccessor;
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.RequestFactory;
+import org.apache.ambari.server.actionmanager.StageFactory;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.AbstractRootServiceResponseFactory;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.AmbariManagementControllerImpl;
+import org.apache.ambari.server.controller.KerberosHelper;
 import org.apache.ambari.server.controller.RequestStatusResponse;
-import org.apache.ambari.server.controller.UserRequest;
 import org.apache.ambari.server.controller.UserResponse;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.Request;
@@ -28,10 +38,30 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
-import org.easymock.EasyMock;
+import org.apache.ambari.server.orm.DBAccessor;
+import org.apache.ambari.server.orm.entities.PermissionEntity;
+import org.apache.ambari.server.orm.entities.PrivilegeEntity;
+import org.apache.ambari.server.scheduler.ExecutionScheduler;
+import org.apache.ambari.server.security.SecurityHelper;
+import org.apache.ambari.server.security.authorization.AmbariGrantedAuthority;
+import org.apache.ambari.server.security.authorization.User;
+import org.apache.ambari.server.security.authorization.Users;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.ConfigFactory;
+import org.apache.ambari.server.state.ServiceComponentFactory;
+import org.apache.ambari.server.state.ServiceComponentHostFactory;
+import org.apache.ambari.server.state.ServiceFactory;
+import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
+import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
+import org.apache.ambari.server.state.stack.OsFamily;
 import org.junit.Assert;
 import org.junit.Test;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.crypto.password.PasswordEncoder;
 
+import javax.persistence.EntityManager;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -41,6 +71,7 @@ import java.util.Set;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 
@@ -128,17 +159,246 @@ public class UserResourceProviderTest {
   }
 
   @Test
-  public void testUpdateResources() throws Exception {
+  public void testUpdateResources_SetAdmin_AsAdminUser() throws Exception {
     Resource.Type type = Resource.Type.User;
+    Injector injector = createInjector();
+
+    SecurityHelper securityHelper = injector.getInstance(SecurityHelper.class);
+    Users users = injector.getInstance(Users.class);
+    User user = createMock(User.class);
+    PrivilegeEntity privilegeEntity = createMock(PrivilegeEntity.class);
+    PermissionEntity permissionEntity = createMock(PermissionEntity.class);
+
+    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);
 
-    AmbariManagementController managementController = createMock(AmbariManagementController.class);
     RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
 
+    Collection<? extends GrantedAuthority> currentAuthorities = Collections.singleton(new AmbariGrantedAuthority(privilegeEntity));
+
     // set expectations
-    managementController.updateUsers(EasyMock.<Set<UserRequest>>anyObject());
+    expect(users.getAnyUser("User100")).andReturn(user).once();
+
+    users.grantAdminPrivilege(1000);
+    expectLastCall().once();
+
+    expect(user.getUserId()).andReturn(1000).once();
+
+    expect(privilegeEntity.getPermission()).andReturn(permissionEntity).once();
+    expect(permissionEntity.getId()).andReturn(PermissionEntity.AMBARI_ADMIN_PERMISSION).once();
+
+    securityHelper.getCurrentAuthorities();
+    expectLastCall().andReturn(currentAuthorities).once();
 
     // replay
-    replay(managementController, response);
+    replay(securityHelper, user, users, privilegeEntity, permissionEntity, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(UserResourceProvider.USER_ADMIN_PROPERTY_ID, "true");
+
+    // create the request
+    Request request = PropertyHelper.getUpdateRequest(properties, null);
+
+    Predicate predicate = new PredicateBuilder()
+        .property(UserResourceProvider.USER_USERNAME_PROPERTY_ID)
+        .equals("User100")
+        .toPredicate();
+    provider.updateResources(request, predicate);
+
+    // verify
+    verify(securityHelper, user, users, privilegeEntity, permissionEntity, response);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testUpdateResources_SetAdmin_AsNonAdminUser() throws Exception {
+    Resource.Type type = Resource.Type.User;
+    Injector injector = createInjector();
+
+    SecurityHelper securityHelper = injector.getInstance(SecurityHelper.class);
+    Users users = injector.getInstance(Users.class);
+    User user = createMock(User.class);
+
+    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);
+
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    // set expectations
+    expect(users.getAnyUser("User100")).andReturn(user).once();
+
+    securityHelper.getCurrentAuthorities();
+    expectLastCall().andReturn(Collections.emptyList()).once();
+
+    // replay
+    replay(securityHelper, user, users, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(UserResourceProvider.USER_ADMIN_PROPERTY_ID, "true");
+
+    // create the request
+    Request request = PropertyHelper.getUpdateRequest(properties, null);
+
+    Predicate predicate = new PredicateBuilder()
+        .property(UserResourceProvider.USER_USERNAME_PROPERTY_ID)
+        .equals("User100")
+        .toPredicate();
+    provider.updateResources(request, predicate);
+
+    // verify
+    verify(securityHelper, user, users, response);
+  }
+
+  @Test
+  public void testUpdateResources_SetActive_AsAdminUser() throws Exception {
+    Resource.Type type = Resource.Type.User;
+    Injector injector = createInjector();
+
+    SecurityHelper securityHelper = injector.getInstance(SecurityHelper.class);
+    Users users = injector.getInstance(Users.class);
+    User user = createMock(User.class);
+    PrivilegeEntity privilegeEntity = createMock(PrivilegeEntity.class);
+    PermissionEntity permissionEntity = createMock(PermissionEntity.class);
+
+    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);
+
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    Collection<? extends GrantedAuthority> currentAuthorities = Collections.singleton(new AmbariGrantedAuthority(privilegeEntity));
+
+    // set expectations
+    expect(users.getAnyUser("User100")).andReturn(user).once();
+
+    users.setUserActive("User100", false);
+    expectLastCall().once();
+
+    expect(user.getUserName()).andReturn("User100").once();
+
+    expect(privilegeEntity.getPermission()).andReturn(permissionEntity).once();
+    expect(permissionEntity.getId()).andReturn(PermissionEntity.AMBARI_ADMIN_PERMISSION).once();
+
+    securityHelper.getCurrentAuthorities();
+    expectLastCall().andReturn(currentAuthorities).once();
+
+    // replay
+    replay(securityHelper, user, users, privilegeEntity, permissionEntity, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(UserResourceProvider.USER_ACTIVE_PROPERTY_ID, "false");
+
+    // create the request
+    Request request = PropertyHelper.getUpdateRequest(properties, null);
+
+    Predicate predicate = new PredicateBuilder()
+        .property(UserResourceProvider.USER_USERNAME_PROPERTY_ID)
+        .equals("User100")
+        .toPredicate();
+    provider.updateResources(request, predicate);
+
+    // verify
+    verify(securityHelper, user, users, privilegeEntity, permissionEntity, response);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testUpdateResources_SetActive_AsNonActiveUser() throws Exception {
+    Resource.Type type = Resource.Type.User;
+    Injector injector = createInjector();
+
+    SecurityHelper securityHelper = injector.getInstance(SecurityHelper.class);
+    Users users = injector.getInstance(Users.class);
+    User user = createMock(User.class);
+
+    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);
+
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    // set expectations
+    expect(users.getAnyUser("User100")).andReturn(user).once();
+
+    securityHelper.getCurrentAuthorities();
+    expectLastCall().andReturn(Collections.emptyList()).once();
+
+    // replay
+    replay(securityHelper, user, users, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(UserResourceProvider.USER_ACTIVE_PROPERTY_ID, "false");
+
+    // create the request
+    Request request = PropertyHelper.getUpdateRequest(properties, null);
+
+    Predicate predicate = new PredicateBuilder()
+        .property(UserResourceProvider.USER_USERNAME_PROPERTY_ID)
+        .equals("User100")
+        .toPredicate();
+    provider.updateResources(request, predicate);
+
+    // verify
+    verify(securityHelper, user, users, response);
+  }
+
+  @Test
+  public void testUpdateResources_SetPassword_AsAdminUser() throws Exception {
+    Resource.Type type = Resource.Type.User;
+    Injector injector = createInjector();
+
+    SecurityHelper securityHelper = injector.getInstance(SecurityHelper.class);
+    Users users = injector.getInstance(Users.class);
+    User user = createMock(User.class);
+    PrivilegeEntity privilegeEntity = createMock(PrivilegeEntity.class);
+    PermissionEntity permissionEntity = createMock(PermissionEntity.class);
+
+    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);
+
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    Collection<? extends GrantedAuthority> currentAuthorities = Collections.singleton(new AmbariGrantedAuthority(privilegeEntity));
+
+    // set expectations
+    expect(users.getAnyUser("User100")).andReturn(user).once();
+
+    users.modifyPassword("User100", "old_password", "password");
+    expectLastCall().once();
+
+    expect(user.getUserName()).andReturn("User100").once();
+
+    expect(privilegeEntity.getPermission()).andReturn(permissionEntity).anyTimes();
+    expect(permissionEntity.getId()).andReturn(PermissionEntity.AMBARI_ADMIN_PERMISSION).anyTimes();
+
+    securityHelper.getCurrentAuthorities();
+    expectLastCall().andReturn(currentAuthorities).anyTimes();
+
+    // replay
+    replay(securityHelper, user, users, privilegeEntity, permissionEntity, response);
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
         type,
@@ -150,18 +410,72 @@ public class UserResourceProviderTest {
     Map<String, Object> properties = new LinkedHashMap<String, Object>();
 
     properties.put(UserResourceProvider.USER_PASSWORD_PROPERTY_ID, "password");
+    properties.put(UserResourceProvider.USER_OLD_PASSWORD_PROPERTY_ID, "old_password");
 
     // create the request
     Request request = PropertyHelper.getUpdateRequest(properties, null);
 
-    Predicate  predicate = new PredicateBuilder().property(UserResourceProvider.USER_USERNAME_PROPERTY_ID).
-        equals("User100").toPredicate();
+    Predicate predicate = new PredicateBuilder()
+        .property(UserResourceProvider.USER_USERNAME_PROPERTY_ID)
+        .equals("User100")
+        .toPredicate();
     provider.updateResources(request, predicate);
 
     // verify
-    verify(managementController, response);
+    verify(securityHelper, user, users, privilegeEntity, permissionEntity, response);
   }
 
+  @Test
+  public void testUpdateResources_SetPassword_AsNonActiveUser() throws Exception {
+    Resource.Type type = Resource.Type.User;
+    Injector injector = createInjector();
+
+    SecurityHelper securityHelper = injector.getInstance(SecurityHelper.class);
+    Users users = injector.getInstance(Users.class);
+    User user = createMock(User.class);
+
+    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);
+
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    // set expectations
+    expect(users.getAnyUser("User100")).andReturn(user).once();
+
+    users.modifyPassword("User100", "old_password", "password");
+    expectLastCall().once();
+
+    expect(user.getUserName()).andReturn("User100").once();
+
+    securityHelper.getCurrentAuthorities();
+    expectLastCall().andReturn(Collections.emptyList()).anyTimes();
+
+    // replay
+    replay(securityHelper, user, users, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(UserResourceProvider.USER_PASSWORD_PROPERTY_ID, "password");
+    properties.put(UserResourceProvider.USER_OLD_PASSWORD_PROPERTY_ID, "old_password");
+
+    // create the request
+    Request request = PropertyHelper.getUpdateRequest(properties, null);
+
+    Predicate predicate = new PredicateBuilder()
+        .property(UserResourceProvider.USER_USERNAME_PROPERTY_ID)
+        .equals("User100")
+        .toPredicate();
+    provider.updateResources(request, predicate);
+
+    // verify
+    verify(securityHelper, user, users, response);
+  }
   @Test
   public void testDeleteResources() throws Exception {
     Resource.Type type = Resource.Type.User;
@@ -188,4 +502,35 @@ public class UserResourceProviderTest {
     // verify
     verify(managementController, response);
   }
+
+  private Injector createInjector() {
+    return Guice.createInjector(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));
+        bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
+        bind(ActionDBAccessor.class).toInstance(createNiceMock(ActionDBAccessor.class));
+        bind(ExecutionScheduler.class).toInstance(createNiceMock(ExecutionScheduler.class));
+        bind(SecurityHelper.class).toInstance(createMock(SecurityHelper.class));
+        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
+        bind(AmbariMetaInfo.class).toInstance(createMock(AmbariMetaInfo.class));
+        bind(ActionManager.class).toInstance(createNiceMock(ActionManager.class));
+        bind(RequestFactory.class).toInstance(createNiceMock(RequestFactory.class));
+        bind(RequestExecutionFactory.class).toInstance(createNiceMock(RequestExecutionFactory.class));
+        bind(StageFactory.class).toInstance(createNiceMock(StageFactory.class));
+        bind(Clusters.class).toInstance(createNiceMock(Clusters.class));
+        bind(AbstractRootServiceResponseFactory.class).toInstance(createNiceMock(AbstractRootServiceResponseFactory.class));
+        bind(ConfigFactory.class).toInstance(createNiceMock(ConfigFactory.class));
+        bind(ConfigGroupFactory.class).toInstance(createNiceMock(ConfigGroupFactory.class));
+        bind(ServiceFactory.class).toInstance(createNiceMock(ServiceFactory.class));
+        bind(ServiceComponentFactory.class).toInstance(createNiceMock(ServiceComponentFactory.class));
+        bind(ServiceComponentHostFactory.class).toInstance(createNiceMock(ServiceComponentHostFactory.class));
+        bind(PasswordEncoder.class).toInstance(createNiceMock(PasswordEncoder.class));
+        bind(KerberosHelper.class).toInstance(createNiceMock(KerberosHelper.class));
+        bind(Users.class).toInstance(createMock(Users.class));
+
+        bind(AmbariManagementController.class).to(AmbariManagementControllerImpl.class);
+      }
+    });
+  }
 }