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} TOP_DATA_TYPES = %w{containers organizations users} # 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