<commit_before>module Authorize
  module Base
    
    PREPOSITIONS = %w(of for in on to at by)
    BOOLEANS = %w(not or and true false)
        
    module EvalParser
      # Parses and evaluates an authorization expression and returns <tt>true</tt> or <tt>false</tt>.
      #
      # The authorization expression is defined by the following grammar:
      #         <expr> ::= (<expr>) | not <expr> | <term> or <expr> | <term> and <expr> | <term>
      #         <term> ::= <role> | <role> <preposition> <model> | <role> <preposition> <class>
      #  <preposition> ::= of | for | in | on | to | at | by
      #        <model> ::= /:*\w+/
      #         <role> ::= /\w+/ | /'.*'/
      #
      # Instead of doing recursive descent parsing (not so fun when we support nested parentheses, etc),
      # we let Ruby do the heavy lifting by identifying terms and replacing them with their boolean equivalents
      # and then letting Ruby eval the resulting expression.
      #
      # 1) Replace all <role> <preposition> <model> matches with their boolean truth state.
      # 2) Replace all <role> matches (carefully avoiding grammer elements) with their boolean truth state.
      # 3) Eval the resulting string

      def parse_authorization_expression( str )
        if str =~ /[^A-Za-z0-9_:'\(\)\s]/
          raise AuthorizationExpressionInvalid, "Invalid authorization expression (#{str})"
          return false
        end
        begin
          expr = replace_role_of_model(str)
          expr = replace_role(expr)
          instance_eval(expr)
        rescue CannotObtainModelObject, CannotObtainUserObject => e
          raise e
          false
        rescue => e
          raise AuthorizationExpressionInvalid, "Cannot parse authorization expression (#{str}):#{e.to_s}"
          false
        end
      end

      def replace_role_of_model(str)
        role_regex = '\s*(\'\s*(.+)\s*\'|(\w+))\s+'
        model_regex = '\s+(:*\w+)'
        parse_regex = Regexp.new(role_regex + '(' + PREPOSITIONS.join('|') + ')' + model_regex)
        str.gsub(parse_regex) do |match|
          " #{process_term($2 || $3, $5)} "
        end
      end

      def replace_role(str)
        role_regex = '\s*(\'\s*(.+)\s*\'|([A-Za-z]\w*))\s*'
        parse_regex = Regexp.new(role_regex)
        str.gsub(parse_regex) do |match|
          if BOOLEANS.include?($3)
            " #{match} "
          else
          " #{process_term($2 || $3)} "
          end
        end
      end
      
      # Determine if any of the authorized_identities are authorized as the given role over the given subject.
      # We cache the authorized roles to optimize the common pattern of "role1 of widget or role2 of widget or role3 of widget".
      def process_term(role, model_name = nil)
        subject = model_name.nil? ? nil : get_model(model_name)
        logger.debug("***Checking for authorization of #{authorized_identities.join(', ')} as #{role} over #{subject.to_s}")
        @authorized_roles ||= {}
        @authorized_roles[subject] ||= Authorization.find_effective(subject, authorized_identities).map(&:role)
        @authorized_roles[subject].include?(role)
      end
    end
  end
end<commit_msg>Refactored parser.<commit_after>module Authorize
  module Base
    PREPOSITIONS = %w(of for in on to at by)
    BOOLEANS = %w(not or and true false)
    
    module EvalParser
      # Parses and evaluates an authorization expression and returns <tt>true</tt> or <tt>false</tt>.
      #
      # The authorization expression is defined by the following grammar:
      #         <expr> ::= (<expr>) | not <expr> | <term> or <expr> | <term> and <expr> | <term>
      #         <term> ::= <role> | <role> <preposition> <model> | <role> <preposition> <class>
      #  <preposition> ::= of | for | in | on | to | at | by
      #        <model> ::= /\w+/
      #         <role> ::= /\w+/
      #
      # We let Ruby do the heavy lifting by identifying terms and replacing them with their boolean equivalents
      # and then letting Ruby eval the resulting boolean expression.
      def parse_authorization_expression(str)
        if str =~ /[^A-Za-z0-9_\(\)\s]/
          raise AuthorizationExpressionInvalid, "Invalid authorization expression (#{str})"
          return false
        end
        begin
          expr = replace_terms(str)
          instance_eval(expr)
        rescue CannotObtainModelObject, CannotObtainUserObject => e
          raise e
        rescue => e
          raise AuthorizationExpressionInvalid, "Cannot parse authorization expression:#{e.to_s}"
        end
      end

      # This method replaces term strings with their processed (boolean) equivalent.  Term strings are either
      # <role> <preposition> <model-or-class> or simply <role>.  The regular expression used to find term strings
      # looks for either pairs of words separated by prepositions or, using negative lookahead, words that are not
      # part of the Ruby boolean language.
      def replace_terms(string)
        re = /(\w+)\s(?:#{PREPOSITIONS.join('|')})\s(\w+)|\b(?!#{BOOLEANS.join('|')})(\w+)\b/
        string.gsub(re) do |match|
          params = [$1, $2, $3].compact
          process_term(*params).to_s
        end
      end

      # Determine if any of the authorized_identities are authorized as the given role over the given subject.
      # We cache the authorized roles to optimize the common pattern of "role1 of widget or role2 of widget or role3 of widget".
      def process_term(role, model_name = nil)
        subject = model_name.nil? ? nil : get_model(model_name)
        logger.debug("***Checking for authorization of #{authorized_identities.join(', ')} as #{role} over #{subject.to_s}")
        @authorized_roles ||= {}
        @authorized_roles[subject] ||= Authorization.find_effective(subject, authorized_identities).map(&:role)
        @authorized_roles[subject].include?(role)
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.12052667116812964, 'fraction_numerical': 0.003713706954760297, 'mean_word_length': 2.4730363423212194, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '292262', 'n_tokens_mistral': 1766, 'n_tokens_neox': 1713, 'n_words': 544}