module ChefZero
  module ChefData
    # Manages translations between REST and ACL data paths
    # and parent paths.
    #
    # Suggestions
    # - make /organizations/ORG/_acl and deprecate organization/_acl and organizations/_acl
    # - add endpoints for /containers/(users|organizations|containers)(/_acl)
    # - add PUT for */_acl
    # - add endpoints for /organizations/ORG/data/containers and /organizations/ORG/cookbooks/containers
    # - sane, fully documented ACL model
    # - sane inheritance / override model: if actors or groups are explicitly
    #   specified on X, they are not inherited from X's parent
    # - stop adding pivotal to acls (he already has access to what he needs)
    module AclPath
      ORG_DATA_TYPES = %w{clients cookbook_artifacts cookbooks containers data environments groups
                          nodes policies policy_groups roles sandboxes}.freeze
      TOP_DATA_TYPES = %w{containers organizations users}.freeze

      # ACL data paths for a partition are:
      # /          -> /acls/root
      # /TYPE      -> /acls/containers/TYPE
      # /TYPE/NAME -> /acls/TYPE/NAME
      #
      # The root partition "/" has its own acls, so it looks like this:
      #
      # / -> /acls/root
      # /users -> /acls/containers/users
      # /organizations -> /acls/containers/organizations
      # /users/schlansky -> /acls/users/schlansky
      #
      # Each organization is its own partition, so it looks like this:
      #
      # /organizations/blah           -> /organizations/blah/acls/root
      # /organizations/blah/roles     -> /organizations/blah/acls/containers/roles
      # /organizations/blah/roles/web -> /organizations/blah/acls/roles/web
      # /organizations/ORG is its own partition.  ACLs for anything under it follow

      # This method takes a Chef REST path and returns the chef-zero path
      # used to look up the ACL.  If an object does not have an ACL directly,
      # it will return nil.  Paths like /organizations/ORG/data/bag/item will
      # return nil, because it is the parent path (data/bag) that has an ACL.
      def self.get_acl_data_path(path)
        # Things under organizations have their own acls hierarchy
        if path[0] == "organizations" && path.size >= 2
          under_org = partition_acl_data_path(path[2..-1], ORG_DATA_TYPES)
          if under_org
            path[0..1] + under_org
          end
        else
          partition_acl_data_path(path, TOP_DATA_TYPES)
        end
      end

      #
      # Reverse transform from acl_data_path to path.
      # /acls/root -> /
      # /acls/** -> /**
      # /organizations/ORG/acls/root -> /organizations/ORG
      # /organizations/ORG/acls/** -> /organizations/ORG/**
      #
      # This means that /acls/containers/nodes maps to
      # /containers/nodes, not /nodes.
      #
      def self.get_object_path(acl_data_path)
        if acl_data_path[0] == "acls"
          if acl_data_path[1] == "root"
            []
          else
            acl_data_path[1..-1]
          end
        elsif acl_data_path[0] == "organizations" && acl_data_path[2] == "acls"
          if acl_data_path[3] == "root"
            acl_data_path[0..1]
          else
            acl_data_path[0..1] + acl_data_path[3..-1]
          end
        end
      end

      # Method *assumes* acl_data_path is valid.
      # /organizations/BLAH's parent is /organizations
      #
      # An example traversal up the whole tree:
      # /organizations/foo/acls/nodes/mario ->
      # /organizations/foo/acls/containers/nodes ->
      # /organizations/foo/acls/containers/containers ->
      # /organizations/foo/acls/root ->
      # /acls/containers/organizations ->
      # /acls/containers/containers ->
      # /acls/root ->
      # nil
      def self.parent_acl_data_path(acl_data_path)
        if acl_data_path[0] == "organizations"
          under_org = partition_parent_acl_data_path(acl_data_path[2..-1])
          if under_org
            acl_data_path[0..1] + under_org
          else
            # ACL data path is /organizations/X/acls/root; therefore parent is "/organizations"
            %w{acls containers organizations}
          end
        else
          partition_parent_acl_data_path(acl_data_path)
        end
      end

      # /acls/root -> nil
      # /acls/containers/containers -> /acls/root
      # /acls/TYPE/X -> /acls/containers/TYPE
      #
      # Method *assumes* acl_data_path is valid.
      # Returns nil if the path is /acls/root
      private_class_method
      def self.partition_parent_acl_data_path(acl_data_path)
        if acl_data_path.size == 3
          if acl_data_path == %w{acls containers containers}
            %w{acls root}
          else
            [ "acls", "containers", acl_data_path[1]]
          end
        else
          nil
        end
      end

      private_class_method
      def self.partition_acl_data_path(path, data_types)
        if path.size == 0
          %w{acls root}
        elsif data_types.include?(path[0])
          if path.size == 0
            [ "acls", "containers", path[0] ]
          elsif path.size == 2
            [ "acls", path[0], path[1] ]
          end
        end
      end
    end
  end
end
