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
   * <p>
   * http://www.apache.org/licenses/LICENSE-2.0
   * <p>
   * 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.atlas.authorize.simple;
  
  import org.apache.atlas.ApplicationProperties;
  import org.apache.atlas.AtlasException;
  import org.apache.atlas.authorize.AtlasAccessRequest;
  import org.apache.atlas.authorize.AtlasAdminAccessRequest;
  import org.apache.atlas.authorize.AtlasAuthorizer;
  import org.apache.atlas.authorize.AtlasEntityAccessRequest;
  import org.apache.atlas.authorize.AtlasPrivilege;
  import org.apache.atlas.authorize.AtlasRelationshipAccessRequest;
  import org.apache.atlas.authorize.AtlasSearchResultScrubRequest;
  import org.apache.atlas.authorize.AtlasTypeAccessRequest;
  import org.apache.atlas.authorize.AtlasTypesDefFilterRequest;
  import org.apache.atlas.authorize.simple.AtlasSimpleAuthzPolicy.AtlasAdminPermission;
  import org.apache.atlas.authorize.simple.AtlasSimpleAuthzPolicy.AtlasAuthzRole;
  import org.apache.atlas.authorize.simple.AtlasSimpleAuthzPolicy.AtlasEntityPermission;
  import org.apache.atlas.authorize.simple.AtlasSimpleAuthzPolicy.AtlasRelationshipPermission;
  import org.apache.atlas.authorize.simple.AtlasSimpleAuthzPolicy.AtlasTypePermission;
  import org.apache.atlas.model.discovery.AtlasSearchResult;
  import org.apache.atlas.model.discovery.AtlasSearchResult.AtlasFullTextResult;
  import org.apache.atlas.model.instance.AtlasEntityHeader;
  import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
  import org.apache.atlas.model.typedef.AtlasTypesDef;
  import org.apache.atlas.utils.AtlasJson;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.collections.MapUtils;
  import org.apache.commons.lang.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.HashSet;
  import java.util.List;
  import java.util.ListIterator;
  import java.util.Set;
  
  import static org.apache.atlas.authorize.AtlasPrivilege.ENTITY_ADD_CLASSIFICATION;
  import static org.apache.atlas.authorize.AtlasPrivilege.ENTITY_REMOVE_CLASSIFICATION;
  import static org.apache.atlas.authorize.AtlasPrivilege.ENTITY_UPDATE_CLASSIFICATION;
  import static org.apache.atlas.authorize.AtlasPrivilege.TYPE_CREATE;
  import static org.apache.atlas.authorize.AtlasPrivilege.TYPE_DELETE;
  import static org.apache.atlas.authorize.AtlasPrivilege.TYPE_READ;
  import static org.apache.atlas.authorize.AtlasPrivilege.TYPE_UPDATE;
  
  public final class AtlasSimpleAuthorizer implements AtlasAuthorizer {
      private static final Logger LOG = LoggerFactory.getLogger(AtlasSimpleAuthorizer.class);
  
      private static final Set<AtlasPrivilege> CLASSIFICATION_PRIVILEGES = new HashSet<>();
      private static final String              REGEX_MATCHALL            = ".*";
      private static final String              WILDCARD_ASTERISK         = "*";
  
      private AtlasSimpleAuthzPolicy authzPolicy;
  
      public AtlasSimpleAuthorizer() {
      }
  
      @Override
      public void init() {
          LOG.info("==> SimpleAtlasAuthorizer.init()");
  
          try (InputStream inputStream = ApplicationProperties.getFileAsInputStream(ApplicationProperties.get(), "atlas.authorizer.simple.authz.policy.file", "atlas-simple-authz-policy.json")) {
              authzPolicy = AtlasJson.fromJson(inputStream, AtlasSimpleAuthzPolicy.class);
  
              addImpliedTypeReadPrivilege(authzPolicy);
          } catch (IOException | AtlasException e) {
              LOG.error("SimpleAtlasAuthorizer.init(): initialization failed", e);
  
              throw new RuntimeException(e);
          }
  
          LOG.info("<== SimpleAtlasAuthorizer.init()");
      }
  
      @Override
      public void cleanUp() {
          LOG.info("==> SimpleAtlasAuthorizer.cleanUp()");
  
          authzPolicy = null;
  
          LOG.info("<== SimpleAtlasAuthorizer.cleanUp()");
      }
  
      @Override
      public boolean isAccessAllowed(AtlasAdminAccessRequest request) {
          LOG.debug("==> SimpleAtlasAuthorizer.isAccessAllowed({})", request);
  
          boolean     ret   = false;
          Set<String> roles = getRoles(request.getUser(), request.getUserGroups());
  
          for (String role : roles) {
              List<AtlasAdminPermission> permissions = getAdminPermissionsForRole(role);
  
              if (permissions != null) {
                  final String action = request.getAction() != null ? request.getAction().getType() : null;
  
                  for (AtlasAdminPermission permission : permissions) {
                      if (isMatch(action, permission.getPrivileges())) {
                          ret = true;
  
                          break;
                      }
                  }
              }
          }
  
          LOG.debug("<== SimpleAtlasAuthorizer.isAccessAllowed({}): {}", request, ret);
  
          return ret;
      }
  
      @Override
      public boolean isAccessAllowed(AtlasEntityAccessRequest request) {
          LOG.debug("==> SimpleAtlasAuthorizer.isAccessAllowed({})", request);
  
          boolean           ret           = false;
          final String      action        = request.getAction() != null ? request.getAction().getType() : null;
          final Set<String> entityTypes   = request.getEntityTypeAndAllSuperTypes();
          final String      entityId      = request.getEntityId();
          final String      attribute     = request.getAttributeName();
          final Set<String> entClsToAuthz = new HashSet<>(request.getEntityClassifications());
          final Set<String> roles         = getRoles(request.getUser(), request.getUserGroups());
  
          for (String role : roles) {
              List<AtlasEntityPermission> permissions = getEntityPermissionsForRole(role);
  
              if (permissions != null) {
                  for (AtlasEntityPermission permission : permissions) {
                      if (isMatch(action, permission.getPrivileges()) && isMatchAny(entityTypes, permission.getEntityTypes()) &&
                              isMatch(entityId, permission.getEntityIds()) && isMatch(attribute, permission.getAttributes()) &&
                              isLabelMatch(request, permission) && isBusinessMetadataMatch(request, permission) && isClassificationMatch(request, permission)) {
                          // 1. entity could have multiple classifications
                          // 2. access for these classifications could be granted by multiple AtlasEntityPermission entries
                          // 3. classifications allowed by the current permission will be removed from entClsToAuthz
                          // 4. request will be allowed once entClsToAuthz is empty i.e. user has permission for all classifications
                          entClsToAuthz.removeIf(entityClassification -> isMatchAny(request.getClassificationTypeAndAllSuperTypes(entityClassification), permission.getEntityClassifications()));
  
                          ret = CollectionUtils.isEmpty(entClsToAuthz);
  
                          if (ret) {
                              break;
                          }
                      }
                  }
              }
          }
  
          if (!ret) {
              LOG.debug("isAccessAllowed={}; classificationsWithNoAccess={}", ret, entClsToAuthz);
          }
  
          LOG.debug("<== SimpleAtlasAuthorizer.isAccessAllowed({}): {}", request, ret);
  
          return ret;
      }
  
      @Override
      public boolean isAccessAllowed(AtlasTypeAccessRequest request) {
          LOG.debug("==> SimpleAtlasAuthorizer.isAccessAllowed({})", request);
  
          boolean ret = false;
  
          Set<String> roles = getRoles(request.getUser(), request.getUserGroups());
  
          for (String role : roles) {
              List<AtlasTypePermission> permissions = getTypePermissionsForRole(role);
  
              if (permissions != null) {
                  final String action       = request.getAction() != null ? request.getAction().getType() : null;
                  final String typeCategory = request.getTypeDef() != null ? request.getTypeDef().getCategory().name() : null;
                  final String typeName     = request.getTypeDef() != null ? request.getTypeDef().getName() : null;
  
                  for (AtlasTypePermission permission : permissions) {
                      if (isMatch(action, permission.getPrivileges()) &&
                              isMatch(typeCategory, permission.getTypeCategories()) &&
                              isMatch(typeName, permission.getTypeNames())) {
                          ret = true;
  
                          break;
                      }
                  }
              }
          }
  
          LOG.debug("<== SimpleAtlasAuthorizer.isAccessAllowed({}): {}", request, ret);
  
          return ret;
      }
  
      @Override
      public boolean isAccessAllowed(AtlasRelationshipAccessRequest request) {
          final Set<String> roles                       = getRoles(request.getUser(), request.getUserGroups());
          final String      relationShipType            = request.getRelationshipType();
          final Set<String> end1EntityTypeAndSuperTypes = request.getEnd1EntityTypeAndAllSuperTypes();
          final Set<String> end1Classifications         = new HashSet<>(request.getEnd1EntityClassifications());
          final String      end1EntityId                = request.getEnd1EntityId();
          final Set<String> end2EntityTypeAndSuperTypes = request.getEnd2EntityTypeAndAllSuperTypes();
          final Set<String> end2Classifications         = new HashSet<>(request.getEnd2EntityClassifications());
          final String      end2EntityId                = request.getEnd2EntityId();
          final String      action                      = request.getAction() != null ? request.getAction().getType() : null;
  
          boolean hasEnd1EntityAccess = false;
          boolean hasEnd2EntityAccess = false;
  
          for (String role : roles) {
              final List<AtlasRelationshipPermission> permissions = getRelationshipPermissionsForRole(role);
  
              if (permissions == null) {
                  continue;
              }
  
              for (AtlasRelationshipPermission permission : permissions) {
                  if (isMatch(relationShipType, permission.getRelationshipTypes()) && isMatch(action, permission.getPrivileges())) {
                      //End1 permission check
                      if (!hasEnd1EntityAccess) {
                          if (isMatchAny(end1EntityTypeAndSuperTypes, permission.getEnd1EntityType()) && isMatch(end1EntityId, permission.getEnd1EntityId())) {
                              end1Classifications.removeIf(entityClassification -> isMatchAny(request.getClassificationTypeAndAllSuperTypes(entityClassification), permission.getEnd1EntityClassification()));
  
                              hasEnd1EntityAccess = CollectionUtils.isEmpty(end1Classifications);
                          }
                      }
  
                      //End2 permission chech
                      if (!hasEnd2EntityAccess) {
                          if (isMatchAny(end2EntityTypeAndSuperTypes, permission.getEnd2EntityType()) && isMatch(end2EntityId, permission.getEnd2EntityId())) {
                              end2Classifications.removeIf(entityClassification -> isMatchAny(request.getClassificationTypeAndAllSuperTypes(entityClassification), permission.getEnd2EntityClassification()));
  
                              hasEnd2EntityAccess = CollectionUtils.isEmpty(end2Classifications);
                          }
                      }
                  }
              }
          }
  
          return hasEnd1EntityAccess && hasEnd2EntityAccess;
      }
  
      @Override
      public void scrubSearchResults(AtlasSearchResultScrubRequest request) {
          LOG.debug("==> SimpleAtlasAuthorizer.scrubSearchResults({})", request);
  
          final AtlasSearchResult result = request.getSearchResult();
  
          if (CollectionUtils.isNotEmpty(result.getEntities())) {
              for (AtlasEntityHeader entity : result.getEntities()) {
                  checkAccessAndScrub(entity, request);
              }
          }
  
          if (CollectionUtils.isNotEmpty(result.getFullTextResult())) {
              for (AtlasFullTextResult fullTextResult : result.getFullTextResult()) {
                  if (fullTextResult != null) {
                      checkAccessAndScrub(fullTextResult.getEntity(), request);
                  }
              }
          }
  
          if (MapUtils.isNotEmpty(result.getReferredEntities())) {
              for (AtlasEntityHeader entity : result.getReferredEntities().values()) {
                  checkAccessAndScrub(entity, request);
              }
          }
  
          LOG.debug("<== SimpleAtlasAuthorizer.scrubSearchResults({}): {}", request, result);
      }
  
      @Override
      public void filterTypesDef(AtlasTypesDefFilterRequest request) {
          AtlasTypesDef typesDef = request.getTypesDef();
  
          filterTypes(request, typesDef.getEnumDefs());
          filterTypes(request, typesDef.getStructDefs());
          filterTypes(request, typesDef.getEntityDefs());
          filterTypes(request, typesDef.getClassificationDefs());
          filterTypes(request, typesDef.getRelationshipDefs());
          filterTypes(request, typesDef.getBusinessMetadataDefs());
      }
  
      private Set<String> getRoles(String userName, Set<String> userGroups) {
          Set<String> ret = new HashSet<>();
  
          if (authzPolicy != null) {
              if (userName != null && authzPolicy.getUserRoles() != null) {
                  List<String> userRoles = authzPolicy.getUserRoles().get(userName);
  
                  if (userRoles != null) {
                      ret.addAll(userRoles);
                  }
              }
  
              if (userGroups != null && authzPolicy.getGroupRoles() != null) {
                  for (String groupName : userGroups) {
                      List<String> groupRoles = authzPolicy.getGroupRoles().get(groupName);
  
                      if (groupRoles != null) {
                          ret.addAll(groupRoles);
                      }
                  }
              }
          }
  
          LOG.debug("<== getRoles({}, {}): {}", userName, userGroups, ret);
  
          return ret;
      }
  
      private List<AtlasAdminPermission> getAdminPermissionsForRole(String roleName) {
          List<AtlasAdminPermission> ret = null;
  
          if (authzPolicy != null && roleName != null) {
              AtlasAuthzRole role = authzPolicy.getRoles().get(roleName);
  
              ret = role != null ? role.getAdminPermissions() : null;
          }
  
          return ret;
      }
  
      private List<AtlasTypePermission> getTypePermissionsForRole(String roleName) {
          List<AtlasTypePermission> ret = null;
  
          if (authzPolicy != null && roleName != null) {
              AtlasAuthzRole role = authzPolicy.getRoles().get(roleName);
  
              ret = role != null ? role.getTypePermissions() : null;
          }
  
          return ret;
      }
  
      private List<AtlasEntityPermission> getEntityPermissionsForRole(String roleName) {
          List<AtlasEntityPermission> ret = null;
  
          if (authzPolicy != null && roleName != null) {
              AtlasAuthzRole role = authzPolicy.getRoles().get(roleName);
  
              ret = role != null ? role.getEntityPermissions() : null;
          }
  
          return ret;
      }
  
      private List<AtlasRelationshipPermission> getRelationshipPermissionsForRole(String roleName) {
          List<AtlasRelationshipPermission> ret = null;
  
          if (authzPolicy != null && roleName != null) {
              AtlasAuthzRole role = authzPolicy.getRoles().get(roleName);
  
              ret = role != null ? role.getRelationshipPermissions() : null;
          }
  
          return ret;
      }
  
      private boolean isMatch(String value, List<String> patterns) {
          boolean ret = value == null;
  
          if (CollectionUtils.isNotEmpty(patterns)) {
              for (String pattern : patterns) {
                  if (isMatch(value, pattern)) {
                      ret = true;
  
                      break;
                  }
              }
          }
  
          if (!ret) {
              LOG.debug("<== isMatch({}, {}): {}", value, patterns, ret);
          }
  
          return ret;
      }
  
      private boolean isMatchAny(Set<String> values, List<String> patterns) {
          boolean ret = CollectionUtils.isEmpty(values);
  
          if (CollectionUtils.isNotEmpty(patterns)) {
              for (String value : values) {
                  if (isMatch(value, patterns)) {
                      ret = true;
  
                      break;
                  }
              }
          }
  
          if (!ret) {
              LOG.debug("<== isMatchAny({}, {}): {}", values, patterns, ret);
          }
  
          return ret;
      }
  
      private boolean isMatch(String value, String pattern) {
          boolean ret;
  
          if (value == null) {
              ret = true;
          } else {
              ret = pattern.equals(REGEX_MATCHALL) || StringUtils.equalsIgnoreCase(value, pattern) || value.matches(pattern);
          }
  
          return ret;
      }
  
      private void checkAccessAndScrub(AtlasEntityHeader entity, AtlasSearchResultScrubRequest request) {
          if (entity != null && request != null) {
              final AtlasEntityAccessRequest entityAccessRequest = new AtlasEntityAccessRequest(request.getTypeRegistry(), AtlasPrivilege.ENTITY_READ, entity, request.getUser(), request.getUserGroups());
  
              entityAccessRequest.setClientIPAddress(request.getClientIPAddress());
  
              if (!isAccessAllowed(entityAccessRequest)) {
                  scrubEntityHeader(entity);
              }
          }
      }
  
      private boolean isLabelMatch(AtlasEntityAccessRequest request, AtlasEntityPermission permission) {
          return !AtlasPrivilege.ENTITY_ADD_LABEL.equals(request.getAction()) && !AtlasPrivilege.ENTITY_REMOVE_LABEL.equals(request.getAction()) || isMatch(request.getLabel(), permission.getLabels());
      }
  
      private boolean isBusinessMetadataMatch(AtlasEntityAccessRequest request, AtlasEntityPermission permission) {
          return !AtlasPrivilege.ENTITY_UPDATE_BUSINESS_METADATA.equals(request.getAction()) || isMatch(request.getBusinessMetadata(), permission.getBusinessMetadata());
      }
  
      private boolean isClassificationMatch(AtlasEntityAccessRequest request, AtlasEntityPermission permission) {
          return !CLASSIFICATION_PRIVILEGES.contains(request.getAction()) || request.getClassification() == null || isMatch(request.getClassification().getTypeName(), permission.getClassifications());
      }
  
      private void filterTypes(AtlasAccessRequest request, List<? extends AtlasBaseTypeDef> typeDefs) {
          if (typeDefs != null) {
              for (ListIterator<? extends AtlasBaseTypeDef> iter = typeDefs.listIterator(); iter.hasNext(); ) {
                  AtlasBaseTypeDef       typeDef     = iter.next();
                  AtlasTypeAccessRequest typeRequest = new AtlasTypeAccessRequest(request.getAction(), typeDef, request.getUser(), request.getUserGroups());
  
                  typeRequest.setClientIPAddress(request.getClientIPAddress());
                  typeRequest.setForwardedAddresses(request.getForwardedAddresses());
                  typeRequest.setRemoteIPAddress(request.getRemoteIPAddress());
  
                  if (!isAccessAllowed(typeRequest)) {
                      iter.remove();
                  }
              }
          }
      }
  
      // add TYPE_READ privilege, if at least one of the following is granted: TYPE_CREATE, TYPE_UPDATE, TYPE_DELETE
      private void addImpliedTypeReadPrivilege(AtlasSimpleAuthzPolicy policy) {
          if (policy != null && policy.getRoles() != null) {
              for (AtlasAuthzRole role : policy.getRoles().values()) {
                  if (role.getTypePermissions() == null) {
                      continue;
                  }
  
                  for (AtlasTypePermission permission : role.getTypePermissions()) {
                      List<String> privileges = permission.getPrivileges();
  
                      if (CollectionUtils.isEmpty(privileges) || privileges.contains(TYPE_READ.name())) {
                          continue;
                      }
  
                      if (privileges.contains(TYPE_CREATE.name()) || privileges.contains(TYPE_UPDATE.name()) || privileges.contains(TYPE_DELETE.name())) {
                          privileges.add(TYPE_READ.name());
                      }
                  }
              }
          }
      }
  
      static {
          CLASSIFICATION_PRIVILEGES.add(ENTITY_ADD_CLASSIFICATION);
          CLASSIFICATION_PRIVILEGES.add(ENTITY_REMOVE_CLASSIFICATION);
          CLASSIFICATION_PRIVILEGES.add(ENTITY_UPDATE_CLASSIFICATION);
      }
  }
  