Welcome to mirror list, hosted at ThFree Co, Russian Federation.

auditor.rb « audit « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4a6e4e2e06e3e8a0139b840fa28c9e6dbe350202 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# frozen_string_literal: true

module Gitlab
  module Audit
    class Auditor
      attr_reader :scope, :name

      # Record audit events
      #
      # @param [Hash] context
      # @option context [String] :name the operation name to be audited, used for error tracking
      # @option context [User] :author the user who authors the change
      # @option context [User, Project, Group] :scope the scope which audit event belongs to
      # @option context [Object] :target the target object being audited
      # @option context [String] :message the message describing the action
      # @option context [Hash] :additional_details the additional details we want to merge into audit event details.
      # @option context [Time] :created_at the time that the event occurred (defaults to the current time)
      #
      # @example Using block (useful when events are emitted deep in the call stack)
      #   i.e. multiple audit events
      #
      #   audit_context = {
      #     name: 'merge_approval_rule_updated',
      #     author: current_user,
      #     scope: project_alpha,
      #     target: merge_approval_rule,
      #     message: 'a user has attempted to update an approval rule'
      #   }
      #
      #   # in the initiating service
      #   Gitlab::Audit::Auditor.audit(audit_context) do
      #     service.execute
      #   end
      #
      #   # in the model
      #   Auditable.push_audit_event('an approver has been added')
      #   Auditable.push_audit_event('an approval group has been removed')
      #
      # @example Using standard method call
      #   i.e. single audit event
      #
      #   merge_approval_rule.save
      #   Gitlab::Audit::Auditor.audit(audit_context)
      #
      # @return result of block execution
      def self.audit(context, &block)
        auditor = new(context)

        return unless auditor.audit_enabled?

        if block
          auditor.multiple_audit(&block)
        else
          auditor.single_audit
        end
      end

      def initialize(context = {})
        @context = context

        @name = @context.fetch(:name, 'audit_operation')
        @stream_only = @context.fetch(:stream_only, false)
        @author = @context.fetch(:author)
        @scope = @context.fetch(:scope)
        @target = @context.fetch(:target)
        @created_at = @context.fetch(:created_at, DateTime.current)
        @message = @context.fetch(:message, '')
        @additional_details = @context.fetch(:additional_details, {})
        @ip_address = @context[:ip_address]
        @target_details = @context[:target_details]
        @authentication_event = @context.fetch(:authentication_event, false)
        @authentication_provider = @context[:authentication_provider]
      end

      def single_audit
        events = [build_event(@message)]

        record(events)
      end

      def multiple_audit
        # For now we dont have any need to implement multiple audit event functionality in CE
        # Defined in EE
      end

      def record(events)
        log_events(events) unless @stream_only
        send_to_stream(events)
      end

      def log_events(events)
        log_authentication_event
        log_to_database(events)
        log_to_file(events)
      end

      def audit_enabled?
        authentication_event?
      end

      def authentication_event?
        @authentication_event
      end

      def log_authentication_event
        return unless Gitlab::Database.read_write? && authentication_event?

        event = AuthenticationEvent.new(authentication_event_payload)
        event.save!
      rescue ActiveRecord::RecordInvalid => e
        ::Gitlab::ErrorTracking.track_exception(e, audit_operation: @name)
      end

      def authentication_event_payload
        {
          # @author can be a User or various Gitlab::Audit authors.
          # Only capture real users for successful authentication events.
          user: author_if_user,
          user_name: @author.name,
          ip_address: Gitlab::RequestContext.instance.client_ip || @author.current_sign_in_ip,
          result: AuthenticationEvent.results[:success],
          provider: @authentication_provider
        }
      end

      def author_if_user
        @author if @author.is_a?(User)
      end

      def send_to_stream(events)
        # Defined in EE
      end

      def build_event(message)
        AuditEvents::BuildService.new(
          author: @author,
          scope: @scope,
          target: @target,
          created_at: @created_at,
          message: message,
          additional_details: @additional_details,
          ip_address: @ip_address,
          target_details: @target_details
        ).execute
      end

      def log_to_database(events)
        AuditEvent.bulk_insert!(events)
      rescue ActiveRecord::RecordInvalid => e
        ::Gitlab::ErrorTracking.track_exception(e, audit_operation: @name)
      end

      def log_to_file(events)
        file_logger = ::Gitlab::AuditJsonLogger.build

        events.each { |event| file_logger.info(log_payload(event)) }
      end

      private

      def log_payload(event)
        payload = event.as_json
        details = formatted_details(event.details)
        payload["details"] = details
        payload.merge!(details).as_json
      end

      def formatted_details(details)
        details.merge(details.slice(:from, :to).transform_values(&:to_s))
      end
    end
  end
end

Gitlab::Audit::Auditor.prepend_mod_with("Gitlab::Audit::Auditor")