app/models/policy.rb
class Policy < ActiveRecord::Base
has_many :permissions,
:dependent => :destroy,
:order => "created_at ASC",
:autosave => true,
:after_add => proc {|policy, perm| perm.policy = policy}
#basically the same as validates_numericality_of :sharing_scope, :access_type
#but with a more generic error message because our users don't know what
#sharing_scope and access_type are.
validates_each(:sharing_scope, :access_type) do |record, attr, value|
raw_value = record.send("#{attr}_before_type_cast") || value
begin
Kernel.Float(raw_value)
rescue ArgumentError, TypeError
record.errors[:base] << "Sharing policy is invalid" unless value.is_a? Integer
end
end
alias_attribute :title, :name
after_commit :queue_update_auth_table
before_save :update_timestamp_if_permissions_change
def update_timestamp_if_permissions_change
update_timestamp if changed_for_autosave?
end
def queue_update_auth_table
unless (previous_changes.keys - ["updated_at"]).empty?
AuthLookupUpdateJob.add_items_to_queue(assets) unless assets.empty?
end
end
def assets
Seek::Util.authorized_types.collect do |type|
type.where(:policy_id=>id)
end.flatten.uniq
end
# *****************************************************************************
# This section defines constants for "sharing_scope" and "access_type" values
# NB! It is critical to all algorithms using these constants, that the latter
# have their integer values increased along with the access they provide
# (so, for example, "editing" should have greated value than "viewing")
# In other words, this means that for both(!) sharing_scope and access_type
# constants it is crucial that order of these (imposed by their integer values)
# is preserved
# sharing_scope
PRIVATE = 0
ALL_SYSMO_USERS = 2
EVERYONE = 4
# access_type
DETERMINED_BY_GROUP = -1 # used for whitelist/blacklist (meaning that it doesn't matter what value this field has)
NO_ACCESS = 0 # i.e. only for anyone; only owner has access
VISIBLE = 1 # visible only
ACCESSIBLE = 2 # accessible and visible
EDITING = 3 # accessible, visible and editing
MANAGING = 4 # any actions that owner of the asset can perform (including "destroy"ing)
PUBLISHING = 5 # publish the item
# "true" value for flag-type fields
TRUE_VALUE = 1
FALSE_VALUE = 0
# *****************************************************************************
#makes a copy of the policy, and its associated permissions.
def deep_copy
copy=self.dup
copied_permissions = self.permissions.collect {|p| p.dup}
copied_permissions.each {|p| copy.permissions << p}
copy
end
#checks that there are permissions for the provided contributor, for the access_type (or higher)
def permission_granted?(contributor,access_type)
permissions.detect{|p| p.contributor==contributor && p.access_type >= access_type}
end
def self.new_for_upload_tool(resource, recipient)
policy = resource.build_policy(:name => 'auto',
:sharing_scope => Policy::PRIVATE,
:access_type => Policy::NO_ACCESS)
policy.permissions.build :contributor_type => "Person", :contributor_id => recipient, :access_type => Policy::ACCESSIBLE
return policy
end
def self.new_from_email(resource, recipients, accessors)
policy = resource.build_policy(:name => 'auto',
:sharing_scope => Policy::PRIVATE,
:access_type => Policy::NO_ACCESS)
recipients.each do |id|
policy.permissions.build :contributor_type => "Person", :contributor_id => id, :access_type => Policy::EDITING
end if recipients
accessors.each do |id|
policy.permissions.build :contributor_type => "Person", :contributor_id => id, :access_type => Policy::ACCESSIBLE
end if accessors
return policy
end
def set_attributes_with_sharing sharing, projects
# if no data about sharing is given, it should be some user (not the owner!)
# who is editing the asset - no need to do anything with policy / permissions: return success
self.tap do |policy|
if sharing
# obtain parameters from sharing hash
policy.sharing_scope = sharing[:sharing_scope]
#MERGENOTE - refactor
policy.access_type = sharing["access_type_#{sharing_scope}"].blank? ? 0 : sharing["access_type_#{sharing_scope}"]
# NOW PROCESS THE PERMISSIONS
# read the permission data from sharing
unless sharing[:permissions].blank? or sharing[:permissions][:contributor_types].blank?
contributor_types = ActiveSupport::JSON.decode(sharing[:permissions][:contributor_types]) || []
new_permission_data = ActiveSupport::JSON.decode(sharing[:permissions][:values]) || {}
else
contributor_types = []
new_permission_data = {}
end
#if share with your project is chosen
#MERGENOTE - refactor
if (sharing[:sharing_scope].to_i == Policy::ALL_SYSMO_USERS) and !projects.map(&:id).compact.blank?
#add Project to contributor_type
contributor_types << "Project" if !contributor_types.include? "Project"
#add one hash {project.id => {"access_type" => sharing[:your_proj_access_type].to_i}} to new_permission_data
new_permission_data["Project"] = {} unless new_permission_data["Project"]
projects.each {|project| new_permission_data["Project"][project.id] = {"access_type" => sharing[:your_proj_access_type].to_i}}
end
# --- Synchronise All Permissions for the Policy ---
# first delete or update any old memberships
policy.permissions.each do |p|
if permission_access = (new_permission_data[p.contributor_type.to_s].try :delete, p.contributor_id.to_s)
p.access_type = permission_access["access_type"]
else
p.mark_for_destruction
end
end
# now add any remaining new memberships
contributor_types.try :each do |contributor_type|
new_permission_data[contributor_type.to_s].try :each do |p|
if policy.new_record? or !Permission.where(:contributor_type => contributor_type, :contributor_id => p[0], :policy_id => policy.id).first
p = policy.permissions.build :contributor_type => contributor_type, :contributor_id => p[0], :access_type => p[1]["access_type"]
end
end
end
end
end
end
# returns a default policy for a project
# (all the related permissions will still be linked to the returned policy)
def self.project_default(project)
# if the default project policy isn't set, NIL will be returned - and the caller
# has to perform further actions in such case
return project.default_policy
end
def self.private_policy
Policy.new(:name => "default private",
:sharing_scope => PRIVATE,
:access_type => NO_ACCESS,
:use_whitelist => false,
:use_blacklist => false)
end
def self.registered_users_accessible_policy
Policy.new(:name => "default accessible",
:sharing_scope => ALL_SYSMO_USERS,
:access_type => ACCESSIBLE,
:use_whitelist => false,
:use_blacklist => false)
end
def self.public_policy
Policy.new(:name => "default public",
:sharing_scope => EVERYONE,
:access_type => ACCESSIBLE
)
end
def self.sysmo_and_projects_policy projects=[]
policy = Policy.new(:name => "default sysmo and projects policy",
:sharing_scope => ALL_SYSMO_USERS,
:access_type => VISIBLE
)
projects.each do |project|
policy.permissions << Permission.new(:contributor => project, :access_type => ACCESSIBLE)
end
return policy
end
#The default policy to use when creating authorized items if no other policy is specified
def self.default resource=nil
#MERGENOTE - would like to revisit this, remove is_virtualiver, and make the default policy itself a configuration
unless Seek::Config.is_virtualliver
private_policy
else
Policy.new(:name => "default accessible", :use_whitelist => false, :use_blacklist => false)
end
end
# translates access type codes into human-readable form
def self.get_access_type_wording(access_type, downloadable=false)
#MERGENOTE - VLN used hard-coded values here that should be moved into the en.yml file
case access_type
when Policy::DETERMINED_BY_GROUP
return I18n.t('access.determined_by_group')
when Policy::NO_ACCESS
return I18n.t("access.no_access")
when Policy::VISIBLE
return downloadable ? I18n.t('access.visible_downloadable') : I18n.t('access.visible')
when Policy::ACCESSIBLE
return downloadable ? I18n.t('access.accessible_downloadable') : I18n.t('access.accessible')
when Policy::EDITING
return downloadable ? I18n.t('access.editing_downloadable') : I18n.t('access.editing')
when Policy::MANAGING
return I18n.t('access.managing')
else
return "Invalid access type"
end
end
# extracts the "settings" of the policy, discarding other information
# (e.g. contributor, creation time, etc.)
def get_settings
settings = {}
settings['sharing_scope'] = self.sharing_scope
settings['access_type'] = self.access_type
settings['use_whitelist'] = self.use_whitelist
settings['use_blacklist'] = self.use_blacklist
return settings
end
# extract the "settings" from all permissions associated to the policy;
# creates array containing 2-item arrays per each policy in the form:
# [ ... , [ permission_id, {"contributor_id" => id, "contributor_type" => type, "access_type" => access} ] , ... ]
def get_permission_settings
p_settings = []
self.permissions.each do |p|
# standard parameters for all contributor types
params_hash = {}
params_hash["contributor_id"] = p.contributor_id
params_hash["contributor_type"] = p.contributor_type
params_hash["access_type"] = p.access_type
params_hash["contributor_name"] = (p.contributor_type == "Person" ? (p.contributor.first_name + " " + p.contributor.last_name) : p.contributor.name)
# some of the contributor types will have special additional parameters
case p.contributor_type
when "FavouriteGroup"
params_hash["whitelist_or_blacklist"] = [FavouriteGroup::WHITELIST_NAME, FavouriteGroup::BLACKLIST_NAME].include?(p.contributor.name)
end
p_settings << [ p.id, params_hash ]
end
return p_settings
end
def private?
sharing_scope == Policy::PRIVATE && permissions.empty?
end
def public?
sharing_scope == Policy::EVERYONE
end
#return the hash: key is access_type, value is the array of people
def summarize_permissions creators=[User.current_user.try(:person)], asset_managers = [], contributor=User.current_user.try(:person)
#build the hash containing contributor_type as key and the people in these groups as value,exception:'Public' holds the access_type as the value
people_in_group = {'Person' => [], 'FavouriteGroup' => [], 'WorkGroup' => [], 'Project' => [], 'Institution' => [], 'WhiteList' => [], 'BlackList' => [],'Network' => [], 'Public' => 0}
#the result return: a hash contain the access_type as key, and array of people as value
grouped_people_by_access_type = {}
policy_to_people_group people_in_group, contributor
permissions_to_people_group permissions, people_in_group
#Now make the people in group unique by choosing the highest access_type
people_in_group['FavouriteGroup'] = remove_duplicate(people_in_group['FavouriteGroup'])
people_in_group['WorkGroup'] = remove_duplicate(people_in_group['WorkGroup'])
people_in_group['Project'] = remove_duplicate(people_in_group['Project'])
people_in_group['Institution'] = remove_duplicate(people_in_group['Institution'])
#Now process precedence with the order [network, institution, project, wg, fg, person]
filtered_people = people_in_group['Network']
filtered_people = precedence(filtered_people, people_in_group['Institution'])
filtered_people = precedence(filtered_people, people_in_group['Project'])
filtered_people = precedence(filtered_people, people_in_group['WorkGroup'])
filtered_people = precedence(filtered_people, people_in_group['FavouriteGroup'])
filtered_people = precedence(filtered_people, people_in_group['Person'])
#add people in white list
filtered_people = add_people_in_whitelist(filtered_people, people_in_group['WhiteList'])
#add people in blacklist
filtered_people = precedence(filtered_people, people_in_group['BlackList'])
#add creators and assign them the Policy::EDITING right
creator_array = creators.collect{|c| [c.id, "#{c.name}", Policy::EDITING] unless c.blank?}
filtered_people = add_people_in_whitelist(filtered_people, creator_array)
#add contributor
filtered_people = add_people_in_whitelist(filtered_people, [[contributor.id, "#{contributor.name}", Policy::MANAGING]]) unless contributor.blank?
#sort people by name
filtered_people = filtered_people.sort{|a,b| a[1] <=> b[1]}
#group people by access_type
grouped_people_by_access_type.merge!(filtered_people.group_by{|person| person[2]})
asset_manager_array = asset_managers.collect { |am| [am.id, "#{am.name}", Policy::MANAGING] unless am.blank? }
if grouped_people_by_access_type[Policy::MANAGING].blank?
grouped_people_by_access_type[Policy::MANAGING] = asset_manager_array
else
grouped_people_by_access_type[Policy::MANAGING] |= asset_manager_array
end
#concat the roles to a person name
concat_roles_to_name grouped_people_by_access_type, creators, asset_managers
#use Policy::DETERMINED_BY_GROUP to store public group if access_type for public > 0
grouped_people_by_access_type[Policy::DETERMINED_BY_GROUP] = people_in_group['Public'] if people_in_group['Public'] > 0
#sort by key of the hash
grouped_people_by_access_type = Hash[grouped_people_by_access_type.sort]
return grouped_people_by_access_type
end
def policy_to_people_group people_in_group, contributor=User.current_user.person
if sharing_scope == Policy::ALL_SYSMO_USERS
people_in_network = get_people_in_network access_type
people_in_group['Network'] |= people_in_network unless people_in_network.blank?
elsif sharing_scope == Policy::EVERYONE
people_in_group['Public'] = access_type
end
#if blacklist/whitelist is used
if use_whitelist
people_in_whitelist = get_people_in_FG(contributor, nil, true, nil)
people_in_group['WhiteList'] |= people_in_whitelist unless people_in_whitelist.blank?
end
#if blacklist/whitelist is used
if use_blacklist
people_in_blacklist = get_people_in_FG(contributor, nil, nil, true)
people_in_group['BlackList'] |= people_in_blacklist unless people_in_blacklist.blank?
end
people_in_group
end
def permissions_to_people_group permissions, people_in_group
permissions.each do |permission|
contributor_id = permission.contributor_id
access_type = permission.access_type
case permission.contributor_type
when 'Person'
person = get_person contributor_id, access_type
people_in_group['Person'] << person unless person.blank?
when 'FavouriteGroup'
people_in_FG = get_people_in_FG nil, contributor_id
people_in_group['FavouriteGroup'] |= people_in_FG unless people_in_FG.blank?
when 'WorkGroup'
people_in_WG = get_people_in_WG contributor_id, access_type
people_in_group['WorkGroup'] |= people_in_WG unless people_in_WG.blank?
when 'Project'
people_in_project = get_people_in_project contributor_id, access_type
people_in_group['Project'] |= people_in_project unless people_in_project.blank?
when 'Institution'
people_in_institution = get_people_in_institution contributor_id, access_type
people_in_group['Institution'] |= people_in_institution unless people_in_institution.blank?
end
end
people_in_group
end
def get_person person_id, access_type
person = begin
Person.find(person_id)
rescue ActiveRecord::RecordNotFound
nil
end
if person
return [person.id, "#{person.name}", access_type]
end
end
#review people WG
def get_people_in_WG wg_id, access_type
w_group = WorkGroup.find(wg_id)
if w_group
people_in_wg = [] #id, name, access_type
w_group.people.each do |person|
people_in_wg.push [person.id, "#{person.name}", access_type ] unless person.blank?
end
end
return people_in_wg
end
#review people in black list, white list and normal workgroup
def get_people_in_FG contributor, fg_id=nil, is_white_list=nil, is_black_list=nil
if is_white_list
f_group = FavouriteGroup.where(["name = ? AND user_id = ?", "__whitelist__", contributor.user.id]).first
elsif is_black_list
f_group = FavouriteGroup.where(["name = ? AND user_id = ?", "__blacklist__", contributor.user.id]).first
else
f_group = FavouriteGroup.find_by_id(fg_id)
end
if f_group
people_in_FG = [] #id, name, access_type
f_group.favourite_group_memberships.each do |fgm|
people_in_FG.push [fgm.person.id, "#{fgm.person.name}", fgm.access_type] if !fgm.blank? and !fgm.person.blank?
end
return people_in_FG
end
end
#review people in project
def get_people_in_project project_id, access_type
project = Project.find(project_id)
if project
people_in_project = [] #id, name, access_type
project.people.each do |person|
people_in_project.push [person.id, "#{person.name}", access_type] unless person.blank?
end
return people_in_project
end
end
#review people in institution
def get_people_in_institution institution_id, access_type
institution = Institution.find(institution_id)
if institution
people_in_institution = [] #id, name, access_type
institution.people.each do |person|
people_in_institution.push [person.id, "#{person.name}", access_type] unless person.blank?
end
return people_in_institution
end
end
#review people in network
def get_people_in_network access_type
people_in_network = [] #id, name, access_type
projects = Project.all
projects.each do |project|
project.people.each do |person|
unless person.blank?
person_identification = [person.id, "#{person.name}"]
people_in_network.push person_identification if (!people_in_network.include? person_identification)
end
end
end
people_in_network.collect!{|person| person.push access_type}
return people_in_network
end
#remove duplicate by taking the one with the highest access_type
def remove_duplicate people_list
result = []
#first replace each person in the people list with the highest access_type of this person
people_list.each do |person|
result.push(get_max_access_type_element(people_list, person))
end
#remove the duplication
result = result.inject([]) { |result,i| result << i unless result.include?(i); result }
result
end
def get_max_access_type_element(array, element)
array.each do |a|
if (element[0] == a[0] && element[2] < a[2])
element = a;
end
end
return element
end
#array2 has precedence
def precedence array1, array2
result = []
result |= array2
array1.each do |a1|
check = false
array2.each do |a2|
if (a1[0] == a2[0])
check = true
break
end
end
if !check
result.push(a1)
end
end
return result
end
#add people which are in whitelist to the people list
def add_people_in_whitelist(people_list, whitelist)
result = []
result |= people_list
result |= whitelist
return remove_duplicate(result)
end
def is_entirely_private? grouped_people_by_access_type, contributor
entirely_private = true
if access_type > Policy::NO_ACCESS
entirely_private = false
else
grouped_people_by_access_type.reject{|key,value| key == Policy::NO_ACCESS || key == Policy::DETERMINED_BY_GROUP}.each_value do |value|
value.each do |person|
entirely_private = false if (person[0] != contributor.try(:id))
end
end
end
return entirely_private
end
def concat_roles_to_name grouped_people_by_access_type, creators, asset_managers
creator_id_array = creators.collect{|c| c.id unless c.blank?}
asset_manage_id_array = asset_managers.collect{|am| am.id unless am.blank?}
grouped_people_by_access_type = grouped_people_by_access_type.reject{|key,value| key == Policy::DETERMINED_BY_GROUP}.each_value do |value|
value.each do |person|
person[1].concat(' (creator)') if creator_id_array.include?(person[0])
person[1].concat(' (asset manager)') if asset_manage_id_array.include?(person[0])
end
end
grouped_people_by_access_type
end
end