Skip to content

Instantly share code, notes, and snippets.

@kporangehat
Last active December 28, 2015 01:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kporangehat/7419853 to your computer and use it in GitHub Desktop.
Save kporangehat/7419853 to your computer and use it in GitHub Desktop.
# Takes 2 PermissionRuleSet code values and does a comparison of all
# entity-level and field-level rules.
#
# The diff is calculated by running PermissionRuleSet.allow? on each
# type of rule and storing the result in a hash for that ruleset. Then
# a diff is run on the two tables to generate the result.
#
# If a field is missing on the current instance, the call to allow? should
# still return the correct result as if the field existed. Missing fields
# are designated with an * before the field name. The ruleset that references
# the missing field is contained in ()'s after the field name.
#
# NOTE: Missing fields can be caused by entity types not being enabled. For
# example, Shotgun will automatically add certain DisplayColumns for
# entity types when they are enabled.
#
# RULE TYPES: The following rule types are checked:
# create_entity
# retire_entity
# see_entity
# see_field
# update_field
# Conditional rules and field rules with value options are not compared
# since they are value dependent. Special admin-type rules are also not
# compared.
#
# Examples:
# > # prints differences to the console
# > diff = PermissionDiff.new
# > diff.compare('artist', 'manager')
# > diff.pp
# ===========================================================================
# Rule differences [artist, artist_int]
# *missing fields on current instance (ruleset)
# ===========================================================================
#
# Asset
# ----------------------------------------
# field Rules
# sg_status_list: {"update_field"=>[false, true]}
# *sg_site_cdn_status (artist_int): {"update_field"=>[false, true]}
#
# Attachment
# ----------------------------------------
# field Rules
# sg_status_list: { "update_field"=>[false, true]}
#
# Booking
# ----------------------------------------
# see_entity: [true, false]
# ...
# ...
#
#
#
# > # outputs differences to a csv file in the directory TMPDIR
# > diff = PermissionDiff.new
# > diff.compare('artist', 'manager')
# > diff.csv
# Wrote CSV file to /var/tmp/PermissionsDiff_artist_manager.csv
# location of csv files
TMPDIR = '/var/tmp'
ENTITY_TYPES = ShotgunConfig.instance['entity.system_types'].sort
DCS_TO_IGNORE = ['cached_display_name']
# borrowed from https://gist.github.com/henrik/146844
# for doing the diff of the 2 ruleset allow tables
class Hash
def deep_diff(b)
a = self
(a.keys | b.keys).inject({}) do |diff, k|
if a[k] != b[k]
if a[k].respond_to?(:deep_diff) && b[k].respond_to?(:deep_diff)
diff[k] = a[k].deep_diff(b[k])
else
diff[k] = [a[k], b[k]]
end
end
diff
end
end
end
class PermissionsDiff
attr_reader :ruleset_a
attr_reader :ruleset_b
attr_reader :table_a
attr_reader :table_b
attr_reader :results
attr_reader :missing_fields
def initialize()
@ruleset_a = {}
@ruleset_b = {}
@table_a = {}
@table_b = {}
@results = {}
@missing_fields = {}
end
# Load up the PermissionRuleSet from Shotgun
#
def load_permission_rule_set(ruleset_code)
rs = PermissionRuleSet.find_by_code(ruleset_code)
if rs.nil?
raise "The PermissionRuleSet '#{ruleset_code}' does not exist!"
end
return rs
end
# Iterate through PermissionRuleSet.permission_rules and return an array
# of any field rules for entity_type that reference a non-existent field
# on the current Shtogun instance
#
def get_rules_with_missing_fields(ruleset, entity_type)
missing_field_rules = []
entity_type_fields = DisplayColumnRegistry.instance.columns_for_class(entity_type).keys
ruleset.permission_rules.each do |r|
next if r.parameter_1 != entity_type
if !r.parameter_2.nil? and !entity_type_fields.include?(r.parameter_2)
missing_field_rules << r
end
end
missing_field_rules
end
# Return table values as a hash declaring whether the specified ruleset allows
# see_field and update_field for the specified entity_type and field_name.
# If the DisplayColumn is read_only or create_only, then that overrides any
# permissions.
def get_field_rules(ruleset, entity_type, field_name, dc_override=nil)
rules = {
'see_field' => ruleset.allow?(:see_field, {:entity_type=>entity_type, :field_name=>field_name})
}
if dc_override.nil?
rules['update_field'] = ruleset.allow?(:update_field, {:entity_type=>entity_type, :field_name=>field_name, :field_value=>nil})
else
rules['update_field'] = dc_override
end
rules
end
# Lookup all fields for entity_type and return a hash representing whether
# rules are allowed for each field. Note that this does not cover any rules
# that are referencing missing fields since they aren't returned by
# DisplayColumnRegistry. We cover that later with add_missing_field_rules_to_table()
#
def get_entity_field_rules(ruleset, entity_type)
return {} if entity_type.nil?
rule_table = {}
entity_type_dcs = DisplayColumnRegistry.instance.columns_for_class(entity_type)
entity_type_dcs.each do |field_name, dc|
next if DCS_TO_IGNORE.include?(field_name)
dc_override = nil
if dc.read_only == true
dc_override = 'read_only'
elsif dc.create_only == true
dc_override = 'create_only'
end
rule_table[field_name] = get_field_rules(ruleset, entity_type, field_name, dc_override)
end
rule_table
end
# Return hash table containing all of the entity-level and field-level rule
# allow? values for ruleset.
#
def get_entity_rules(ruleset, entity_type=nil)
rule_table = {
'see_entity' => ruleset.allow?(:see_entity, {:entity_type=>entity_type}),
'create_entity' => ruleset.allow?(:create_entity, {:entity_type=>entity_type}),
'retire_entity' => ruleset.allow?(:retire_entity, {:entity_type=>entity_type})
}
field_rules = {'fields' => get_entity_field_rules(ruleset, entity_type)}
rule_table.merge!(field_rules)
rule_table
end
# Return complete hash table representing all of the entity-level and
# field-level rule allow? values for ruleset. Include blanket rules that
# have no entity_type and assigned them with the 'ALL' key.
#
# Store any field-level rules that reference a non-existent field a
# separate key
def build_rule_table(ruleset)
table = {}
table['ALL'] = get_entity_rules(ruleset, nil)
table['ALL']['missing_fields'] = []
ENTITY_TYPES.each do |et|
table[et] = get_entity_rules(ruleset, et)
table[et]['missing_fields'] = get_rules_with_missing_fields(ruleset, et)
end
table
end
# Add any missing field rules to the existing tables since they didn't get
# run previously. Even if the fieldname doesn't exists on the server,
# ruleset.allow? should return the extected result. Delete the missing_fields
# key in the tables to clean things up.
#
def add_missing_field_rules_to_tables()
[@table_a, @table_b].each do |table|
table.each do |et, info|
info['missing_fields'].each do |r|
@table_a[et]['fields']["*#{r.parameter_2} (#{r.permission_rule_set.code})"] = get_field_rules(@ruleset_a, et, r.parameter_2)
@table_b[et]['fields']["*#{r.parameter_2} (#{r.permission_rule_set.code})"] = get_field_rules(@ruleset_b, et, r.parameter_2)
end
info['missing_fields'].each do |r|
rs_code = r.permission_rule_set.code
@missing_fields[rs_code] << "#{et}.#{r.parameter_2}"
end
info.delete('missing_fields')
end
end
end
# Loads two rulesets by code, populates hash table tracking whether
# each rule type is allowed or not. Generates a diff table with the
# differences between the two ruleset allow tables.
#
def compare(rs_code_a, rs_code_b)
@ruleset_a = load_permission_rule_set(rs_code_a)
@ruleset_b = load_permission_rule_set(rs_code_b)
@missing_fields = {rs_code_a=>[], rs_code_b=>[]}
@table_a = build_rule_table(@ruleset_a)
@table_b = build_rule_table(@ruleset_b)
add_missing_field_rules_to_tables()
@results = @table_a.deep_diff(@table_b)
nil
end
# Prints results of diff comparison of the two rulesets to the console.
#
def pp()
puts "="*80
puts " Rule differences [#{@ruleset_a.code}, #{@ruleset_b.code}] "
puts " *missing fields on current instance (ruleset)"
puts "="*80
@results.each do |et, info|
puts
puts et
puts "-"*40
info.each do |rule_type, e_diffs|
if rule_type == 'fields'
puts " field Rules"
e_diffs.each do |field, f_diffs|
puts "\t #{field}: #{f_diffs}"
end
else
puts " #{rule_type}: #{e_diffs}"
end
end
end
puts
end
# Writes a CSV file with diff results between two rulesets. This is useful
# for delivering reports to clients.
#
def csv()
require 'csv'
filename = "PermissionsDiff_#{@ruleset_a.code}_#{@ruleset_b.code}.csv"
CSV.open("#{TMPDIR}/#{filename}", "wb") do |csv|
# csv_data = CSV.generate do |csv|
csv << ['ENTITY TYPE', 'FIELD', 'RULE', @ruleset_a.code, @ruleset_b.code]
@results.each do |et, info|
info.each do |rule_type, e_diffs|
if rule_type == 'fields'
e_diffs.each do |field, f_diffs|
f_diffs.each do |rule, allow|
csv << [et, field, rule, allow[0], allow[1]]
end
end
else
csv << [et, nil, rule_type, e_diffs[0], e_diffs[1]]
end
end
end
# end
end
puts "Wrote CSV file to #{TMPDIR}/#{filename}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment