<commit_before>require 'project_compat'
module Acts
  module Authorized
    module PolicyBasedAuthorization
      def self.included klass
        klass.extend ClassMethods
        klass.extend AuthLookupMethods
        klass.class_eval do
          belongs_to :contributor, :polymorphic => true unless method_defined? :contributor
          after_initialize :contributor_or_default_if_new

          #checks a policy exists, and if missing resorts to using a private policy
          after_initialize :policy_or_default_if_new

          include ProjectCompat unless method_defined? :projects

          belongs_to :policy, :required_access_to_owner => :manage, :autosave => true

          after_save :queue_update_auth_table
        end
      end

      module ClassMethods

      end

      module AuthLookupMethods
        def all_authorized_for action, user=User.current_user
          user_id = user.nil? ? 0 : user.id
          c = lookup_count_for_action_and_user user_id
          if (c==count)
            Rails.logger.warn("Lookup table is complete for user_id = #{user_id}")
            ids = lookup_ids_for_person_and_action action, user_id
            find_all_by_id(ids)
          else
            Rails.logger.warn("Lookup table is incomplete for user_id = #{user_id} - doing things the slow way")
            #trigger background task to update table
            all.select { |df| df.send("can_#{action}?") }
          end
        end

        def lookup_table_name
          "#{self.name.underscore}_auth_lookup"
        end

        def clear_lookup_table
          ActiveRecord::Base.connection.execute("delete from #{lookup_table_name}")
        end

        def lookup_ids_for_person_and_action action,user_id
          ActiveRecord::Base.connection.select_all("select asset_id from #{lookup_table_name} where user_id = #{user_id} and can_#{action}=#{ActiveRecord::Base.connection.quoted_true}").collect{|k| k.values}.flatten
        end

        def lookup_count_for_action_and_user user_id
          ActiveRecord::Base.connection.select_one("select count(*) from #{lookup_table_name} where user_id = #{user_id}").values[0].to_i
        end

        def lookup_for_asset action,user_id,asset_id
          attribute = "can_#{action}"
          res = ActiveRecord::Base.connection.select_one("select #{attribute} from #{lookup_table_name} where user_id=#{user_id} and asset_id=#{asset_id}")
          if res.nil?
            nil
          else
            res[attribute] == ActiveRecord::Base.connection.quoted_true
          end
        end
      end

      AUTHORIZATION_ACTIONS.each do |action|
          eval <<-END_EVAL
            def can_#{action}? user = User.current_user
                return true if new_record?
                if skip_lookup
                  lookup=nil
                else
                  user_id = user.nil? ? 0 : user.id
                  lookup = self.class.lookup_for_asset("#{action}", user_id,self.id)
                end

                if lookup.nil?
                  perform_auth(user,"#{action}")
                else
                  lookup
                end
            end
          END_EVAL
      end

      def queue_update_auth_table
        #FIXME: somewhat aggressively does this after every save - this is to cover projects changing - can be refined in the future
        unless (self.changed - ["updated_at", "last_used_at"]).empty?
          AuthLookupUpdateJob.add_items_to_queue self
        end
      end

      def update_lookup_table user=nil
        user_id = user.nil? ? 0 : user.id

        sql = "delete from #{self.class.lookup_table_name} where user_id=#{user_id} and asset_id=#{id}"
        ActiveRecord::Base.connection.execute(sql)
        can_view = ActiveRecord::Base.connection.quote perform_auth(user,"view")
        can_edit = ActiveRecord::Base.connection.quote perform_auth(user,"edit")
        can_download = ActiveRecord::Base.connection.quote perform_auth(user,"download")
        can_manage = ActiveRecord::Base.connection.quote perform_auth(user,"manage")
        can_delete = ActiveRecord::Base.connection.quote perform_auth(user,"delete")
        sql = "insert into #{self.class.lookup_table_name} (user_id,asset_id,can_view,can_edit,can_download,can_manage,can_delete) values (#{user_id},#{id},#{can_view},#{can_edit},#{can_download},#{can_manage},#{can_delete});"
        ActiveRecord::Base.connection.execute(sql)

      end

      def contributor_credited?
        true
      end

      def private?
        policy.private?
      end

      def public?
        policy.public?
      end

      def default_policy<commit_msg>update the auth lookup record, rather than delete and insert, if possible<commit_after>require 'project_compat'
module Acts
  module Authorized
    module PolicyBasedAuthorization
      def self.included klass
        klass.extend ClassMethods
        klass.extend AuthLookupMethods
        klass.class_eval do
          belongs_to :contributor, :polymorphic => true unless method_defined? :contributor
          after_initialize :contributor_or_default_if_new

          #checks a policy exists, and if missing resorts to using a private policy
          after_initialize :policy_or_default_if_new

          include ProjectCompat unless method_defined? :projects

          belongs_to :policy, :required_access_to_owner => :manage, :autosave => true

          after_save :queue_update_auth_table
        end
      end

      module ClassMethods

      end

      module AuthLookupMethods
        def all_authorized_for action, user=User.current_user
          user_id = user.nil? ? 0 : user.id
          c = lookup_count_for_action_and_user user_id
          last_stored_asset_id = last_asset_id_for_user user_id
          last_asset_id = self.last(:order=>:id).try(:id)

          #cannot rely purly on the count, since an item could have been deleted and a new one added
          if (c==count && last_stored_asset_id == last_asset_id)
            Rails.logger.warn("Lookup table is complete for user_id = #{user_id}")
            ids = lookup_ids_for_person_and_action action, user_id
            find_all_by_id(ids)
          else
            Rails.logger.warn("Lookup table is incomplete for user_id = #{user_id} - doing things the slow way")
            if (c==0 && !last_asset_id.nil?)
              AuthLookupUpdateJob.add_items_to_queue user
            end
            all.select { |df| df.send("can_#{action}?") }
          end
        end

        def lookup_table_name
          "#{self.name.underscore}_auth_lookup"
        end

        def clear_lookup_table
          ActiveRecord::Base.connection.execute("delete from #{lookup_table_name}")
        end

        def lookup_ids_for_person_and_action action,user_id
          ActiveRecord::Base.connection.select_all("select asset_id from #{lookup_table_name} where user_id = #{user_id} and can_#{action}=#{ActiveRecord::Base.connection.quoted_true}").collect{|k| k.values}.flatten
        end

        def lookup_count_for_action_and_user user_id
          ActiveRecord::Base.connection.select_one("select count(*) from #{lookup_table_name} where user_id = #{user_id}").values[0].to_i
        end

        def last_asset_id_for_user user_id
          v = ActiveRecord::Base.connection.select_one("select max(asset_id) from #{lookup_table_name} where user_id = #{user_id}").values[0]
          v.nil? ? -1 : v.to_i
        end

        def lookup_for_asset action,user_id,asset_id
          attribute = "can_#{action}"
          res = ActiveRecord::Base.connection.select_one("select #{attribute} from #{lookup_table_name} where user_id=#{user_id} and asset_id=#{asset_id}")
          if res.nil?
            nil
          else
            res[attribute] == ActiveRecord::Base.connection.quoted_true
          end
        end
      end

      AUTHORIZATION_ACTIONS.each do |action|
          eval <<-END_EVAL
            def can_#{action}? user = User.current_user
                return true if new_record?
                user_id = user.nil? ? 0 : user.id
                lookup = self.class.lookup_for_asset("#{action}", user_id,self.id)

                if lookup.nil?
                  perform_auth(user,"#{action}")
                else
                  lookup
                end
            end
          END_EVAL
      end

      def queue_update_auth_table
        #FIXME: somewhat aggressively does this after every save can be refined in the future
        unless (self.changed - ["updated_at", "last_used_at"]).empty?
          AuthLookupUpdateJob.add_items_to_queue self
        end
      end

      def update_lookup_table user=nil
        user_id = user.nil? ? 0 : user.id

        can_view = ActiveRecord::Base.connection.quote perform_auth(user,"view")
        can_edit = ActiveRecord::Base.connection.quote perform_auth(user,"edit")
        can_download = ActiveRecord::Base.connection.quote perform_auth(user,"download")
        can_manage = ActiveRecord::Base.connection.quote perform_auth(user,"manage")
        can_delete = ActiveRecord::Base.connection.quote perform_auth(user,"delete")

        #check to see if an insert of update is needed, action used is arbitary
        lookup = self.class.lookup_for_asset("view",user_id,self.id)
        insert = lookup.nil?

        if insert
          sql = "insert into #{self.class.lookup_table_name} (user_id,asset_id,can_view,can_edit,can_download,can_manage,can_delete) values (#{user_id},#{id},#{can_view},#{can_edit},#{can_download},#{can_manage},#{can_delete});"
        else
          sql = "update #{self.class.lookup_table_name} set can_view=#{can_view}, can_edit=#{can_edit}, can_download=#{can_download},can_manage=#{can_manage},can_delete=#{can_delete} where user_id=#{user_id} and asset_id=#{id}"
        end

        ActiveRecord::Base.connection.execute(sql)

      end

      def contributor_credited?
        true
      end

      def private?
        policy.private?
      end

      def public?
        policy.public?
      end

      def default_policy
{'fraction_non_alphanumeric': 0.08850174216027874, 'fraction_numerical': 0.001095072175211548, 'mean_word_length': 2.513816019587268, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1536292', 'n_tokens_mistral': 2911, 'n_tokens_neox': 2841, 'n_words': 636}