polywiki/src/models/acl.cr

67 lines
2.1 KiB
Crystal

class Acl < Jennifer::Model::Base
with_timestamps
mapping(
id: { type: UUID, primary: true, default: UUID.random },
role: String,
path: String,
permission: String,
created_at: { type: Time, default: Time.utc },
updated_at: { type: Time, default: Time.utc },
)
end
# Load permissions, ...
require "./acl/*"
class Acl
alias Role = String
alias PermissionTuple = NamedTuple(path: Acl::Path, permission: Permission)
@@permissions = Hash(Role, Array(PermissionTuple)).new
def self._permissions
@@permissions
end
after_save :reload_permissions!
private def reload_permissions!
Acl.reload_permissions!
end
def self.reload_permissions!
# clean up existing permissions
# TODO optimise with optional role argument to clean part of it
@@permissions = Hash(Role, Array(PermissionTuple)).new
Acl.all.each do |acl|
@@permissions[acl.role] = Array(PermissionTuple).new if !@@permissions.has_key?(acl.role)
@@permissions[acl.role] << {
path: Acl::Path.new(acl.path),
# TODO handle invalid string
permission: STRING_TO_PERMISSION[acl.permission],
}
end
end
# check if the database allow the role to perform an action on a path
# TODO: handle invalid string
def self.permitted?(role : String, path : String, permission : String) : Bool
permitted?(role: role, path: path, permission: STRING_TO_PERMiSSION[permission])
end
# check if the database allow the role to perform an action on a path
def self.permitted?(role : String, path : String, permission : Permission) : Bool
# role do not exists ? => false TODO use "default" configuration
return false if !@@permissions.has_key?(role)
matched_permissions = @@permissions[role].select { |pp_tuple| pp_tuple[:path].match?(path) }
# path not mapped at all ? => false TODO use "default" configuration
return false if matched_permissions.empty?
# keep the longuest path
match = matched_permissions.reduce { |l, r| l[:path].size >= r[:path].size ? l : r }
match[:permission].to_i >= permission.to_i
end
end