Upgrade to Pro — share decks privately, control downloads, hide ads and more …

flexible-authorization

 flexible-authorization

Flexible Authorization: Storing & Managing Rules in Database presentation for RDRC 2016

Giovanni Sakti

June 23, 2016
Tweet

More Decks by Giovanni Sakti

Other Decks in Programming

Transcript

  1. U: Can you make role A able to read this

    document after its state changes to approved? Me: *(hack the source code a bit)* Done U: Can you make role B release this document after at least 5 people agree? Me: *(hack the source code a bit)* Done U: *(Insert some very specific requests here)* Me: *(hack the source code a bit)* Done
  2. U: Can you just give me a nice interfaces to

    work with, so that I don’t have to bug you anymore when new requirements appear?
  3. users roles activities Defining Tables & Columns N N N

    N username password e-mail name name actions : JSONB object conditions : JSONB Regulating
  4. actions: ['READ'], object: 'Document', conditions: [ "object.state = 'APPROVED'" ]

    actions: ['RELEASE'], object: 'Document', conditions: [ "object.approver_count >= 5" ] Regulating
  5. actions: ['READ', 'UPDATE', 'DELETE'], object: 'Document', conditions: [ "AND", [

    "OR", "object.organization.code = 'A000'", "object.organization.code = ‘B000'" ], "object.state = 'RELEASED" ] Regulating
  6. actions: ['READ', 'UPDATE', 'DELETE'], object: 'Document', conditions: [ "AND", [

    "OR", "object.organization.code = 'A000'", "object.organization.code = ‘B000'" ], "object.state = 'RELEASED" ] Evaluate conditions tree Evaluating
  7. actions: ['READ', 'UPDATE', 'DELETE'], object: 'Document', conditions: [ "AND", [

    "OR", "object.organization.code = 'A000'", "object.organization.code = ‘B000'" ], "object.state = 'RELEASED" ] Scoping def parse_json(opts = {}) instruction = {} instruction[:joins] = [] instruction[:selects] = {} # If json_arr is blank if opts[:json_arr].blank? instruction[:query] = TRUE_QUERY return instruction end # If json_arr is not blank operator = nil operand_stack = [] opts[:json_arr].each_with_index do |token, idx| if (idx == 0) && (%w(AND OR).include? token) operator = token else if token.is_a? Array temp = parse_json(opts.merge({json_arr: token})) instruction[:joins] |= temp[:joins].flatten instruction[:selects].merge! temp[:selects] query = "(#{temp[:query]})" else query_tokens = token.scan(/(?:"(?:\\.|[^"])*"|[^" ])+/) if (query_tokens.size != 3) && (!%w(= IN).include? query_tokens[1]) raise "Syntax error #{token}" else lhs_join = nil lhs_select = nil lhs_query = "" lhs_arr = query_tokens[0].split(".") lhs_arr.to_enum.with_index.reverse_each do |atom, idx| if idx == lhs_arr.size - 1 lhs_query = atom lhs_select = {"#{atom}" => lhs_query} elsif idx < lhs_arr.size - 1 && idx > 0 if lhs_join.nil? lhs_join = atom.to_sym else lhs_join = {atom.to_sym => lhs_join} end lhs_query = "#{atom}.#{lhs_arr[lhs_arr.size - 1]}" lhs_select = {"#{lhs_arr[lhs_arr.size - 1]}" => lhs_query} else # If array size is 2, then we should rename object to avoid ambiguity sql statement if lhs_arr.size == 2 lhs_query = "#{opts[:object].table_name}.#{lhs_arr[lhs_arr.size - 1]}" lhs_select = {"#{lhs_arr[lhs_arr.size - 1]}" => lhs_query} end end end rhs_query = "" if query_tokens[2].start_with? "subject" subject_scope = opts[:subject] rhs_arr = query_tokens[2].split(".") rhs_arr.each_with_index do |atom, idx| # If subject is not exist then there is no need to construct query, move along unless subject_scope rhs_query = "" next end if idx == 0 # NOP elsif idx > 0 && idx < (rhs_arr.size - 1) subject_scope = subject_scope.send(atom.to_sym) end if idx == (rhs_arr.size - 1) if query_tokens[1] == "IN" values = subject_scope.map{|m| "'#{m.send(atom.to_sym)}'"} rhs_query = "(#{values.join(',')})" if values.present? else rhs_query = "'#{subject_scope.send(atom.to_sym)}'" end end end else rhs_query = query_tokens[2] end instruction[:joins] |= [lhs_join] if lhs_join.present? instruction[:selects].merge! lhs_select if lhs_select.present? if rhs_query.present? query = "#{lhs_query} #{query_tokens[1]} #{rhs_query}" else query = FALSE_QUERY end end end operand_stack.push query end end if operator instruction[:query] = operand_stack.join(" #{operator} ") else instruction[:query] = operand_stack.shift end return instruction end What to select? What to join? What to filter?
  8. The current focus is on how to make our authorization

    system less dependent on developers and give power to user