content: // 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.cloudstack.acl;
  
  import java.util.List;
  import java.util.Map;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
  
  import org.apache.cloudstack.context.CallContext;
  
  import com.cloud.exception.PermissionDeniedException;
  import com.cloud.exception.UnavailableCommandException;
  import com.cloud.projects.Project;
  import com.cloud.projects.ProjectAccount;
  import com.cloud.projects.dao.ProjectAccountDao;
  import com.cloud.user.Account;
  import com.cloud.user.AccountService;
  import com.cloud.user.User;
  import com.cloud.utils.component.AdapterBase;
  import com.cloud.utils.component.PluggableService;
  
  public class ProjectRoleBasedApiAccessChecker  extends AdapterBase implements APIAclChecker {
  
      @Inject
      ProjectAccountDao projectAccountDao;
      @Inject
      ProjectRoleService projectRoleService;
      @Inject
      RoleService roleService;
      @Inject
      AccountService accountService;
  
      private List<PluggableService> services;
      protected ProjectRoleBasedApiAccessChecker() {
          super();
      }
  
      private void denyApiAccess(final String commandName) throws PermissionDeniedException {
          throw new PermissionDeniedException("The API " + commandName + " is denied for the user's/account's project role.");
      }
  
      @Override
      public boolean isEnabled() {
          if (!roleService.isEnabled()) {
              logger.trace("RoleService is disabled. We will not use ProjectRoleBasedApiAccessChecker.");
          }
          return roleService.isEnabled();
      }
  
      @Override
      public List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException {
          if (!isEnabled()) {
              return apiNames;
          }
  
          Project project = CallContext.current().getProject();
          if (project == null) {
              if (logger.isTraceEnabled()) {
                  logger.trace(String.format("Project is null, ProjectRoleBasedApiAccessChecker only applies to projects, returning APIs [%s] for user [%s] as allowed.", apiNames, user));
              }
              return apiNames;
          }
  
          long accountID = user.getAccountId();
          ProjectAccount projectUser = projectAccountDao.findByProjectIdUserId(project.getId(), accountID, user.getId());
          if (projectUser != null) {
              if (projectUser.getAccountRole() != ProjectAccount.Role.Admin) {
                  apiNames.removeIf(apiName -> !isPermitted(project, projectUser, apiName));
              }
              if (logger.isTraceEnabled()) {
                  logger.trace(String.format("Returning APIs [%s] as allowed for user [%s].", apiNames, user));
              }
              return apiNames;
          }
  
          ProjectAccount projectAccount = projectAccountDao.findByProjectIdAccountId(project.getId(), accountID);
          if (projectAccount == null) {
              throw new PermissionDeniedException(String.format("The user [%s] does not belong to the project [%s].", user, project));
          }
  
          if (projectAccount.getAccountRole() != ProjectAccount.Role.Admin) {
              apiNames.removeIf(apiName -> !isPermitted(project, projectAccount, apiName));
          }
          if (logger.isTraceEnabled()) {
              logger.trace(String.format("Returning APIs [%s] as allowed for user [%s].", apiNames, user));
          }
          return apiNames;
      }
  
      @Override
      public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException {
          if (!isEnabled()) {
              return true;
          }
  
          Project project = CallContext.current().getProject();
          if (project == null) {
              if (logger.isTraceEnabled()) {
                  logger.trace(String.format("Project is null, ProjectRoleBasedApiAccessChecker only applies to projects, returning API [%s] for user [%s] as allowed.", apiCommandName,
                  user));
              }
              return true;
          }
  
          Account userAccount = accountService.getAccount(user.getAccountId());
          if (accountService.isRootAdmin(userAccount.getId()) || accountService.isDomainAdmin(userAccount.getAccountId())) {
              logger.info(String.format("Account [%s] is Root Admin or Domain Admin, all APIs are allowed.", userAccount.getAccountName()));
              return true;
          }
  
          ProjectAccount projectUser = projectAccountDao.findByProjectIdUserId(project.getId(), userAccount.getAccountId(), user.getId());
          if (projectUser != null) {
              if (projectUser.getAccountRole() == ProjectAccount.Role.Admin || isPermitted(project, projectUser, apiCommandName)) {
                  return true;
              }
              denyApiAccess(apiCommandName);
          }
  
          ProjectAccount projectAccount = projectAccountDao.findByProjectIdAccountId(project.getId(), userAccount.getAccountId());
          if (projectAccount != null) {
              if (projectAccount.getAccountRole() == ProjectAccount.Role.Admin || isPermitted(project, projectAccount, apiCommandName)) {
                  return true;
              }
              denyApiAccess(apiCommandName);
          }
  
          // Default deny all
          if ("updateProjectInvitation".equals(apiCommandName)) {
              return true;
          }
  
          throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for this account/user in project [%s].", apiCommandName, project.getUuid()));
      }
  
      @Override
      public boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException {
          return true;
      }
  
      public boolean isPermitted(Project project, ProjectAccount projectUser, String ... apiCommandNames) {
          ProjectRole projectRole = null;
          if(projectUser.getProjectRoleId() != null) {
              projectRole = projectRoleService.findProjectRole(projectUser.getProjectRoleId(), project.getId());
          }
  
          if (projectRole == null) {
              return true;
          }
  
          List<ProjectRolePermission> allProjectRolePermissions = projectRoleService.findAllProjectRolePermissions(project.getId(), projectRole.getId());
          for (String api : apiCommandNames) {
              for (ProjectRolePermission permission : allProjectRolePermissions) {
                  if (permission.getRule().matches(api)) {
                      return Permission.ALLOW.equals(permission.getPermission());
                  }
              }
          }
  
          return true;
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          super.configure(name, params);
          return true;
      }
  
      @Override
      public boolean start() {
          return super.start();
      }
  
      public List<PluggableService> getServices() {
          return services;
      }
  
      @Inject
      public void setServices(List<PluggableService> services) {
          this.services = services;
      }
  }
  