Module: Legion::Extensions::Github::App::Runners::Webhooks

Includes:
Helpers::Client, Helpers::Lex
Included in:
Client
Defined in:
lib/legion/extensions/github/app/runners/webhooks.rb

Constant Summary collapse

SCOPE_INVALIDATION_EVENTS =
%w[installation installation_repositories].freeze

Constants included from Helpers::Client

Helpers::Client::CREDENTIAL_RESOLVERS

Constants included from Helpers::TokenCache

Helpers::TokenCache::TOKEN_BUFFER_SECONDS

Instance Method Summary collapse

Methods included from Helpers::Client

#connection, #gh_cli_token_output, #max_fallback_retries, #on_rate_limit, #on_scope_authorized, #on_scope_denied, #resolve_broker_app, #resolve_credential, #resolve_env, #resolve_gh_cli, #resolve_next_credential, #resolve_settings_app, #resolve_settings_delegated, #resolve_settings_pat, #resolve_vault_app, #resolve_vault_delegated, #resolve_vault_pat

Methods included from Helpers::ScopeRegistry

#credential_fingerprint, #invalidate_scope, #mark_rate_limited, #rate_limited?, #register_scope, #scope_status

Methods included from Helpers::TokenCache

#fetch_token, #mark_rate_limited, #rate_limited?, #store_token

Instance Method Details

#invalidate_all_scopes_for_owner(owner:) ⇒ Object



54
55
56
57
58
59
# File 'lib/legion/extensions/github/app/runners/webhooks.rb', line 54

def invalidate_all_scopes_for_owner(owner:)
  known_fingerprints = resolve_known_fingerprints
  known_fingerprints.each do |fp|
    invalidate_scope(fingerprint: fp, owner: owner)
  end
end

#invalidate_scopes_for_event(event_type:, payload:) ⇒ Object



45
46
47
48
49
50
51
52
# File 'lib/legion/extensions/github/app/runners/webhooks.rb', line 45

def invalidate_scopes_for_event(event_type:, payload:, **)
  return unless SCOPE_INVALIDATION_EVENTS.include?(event_type.to_s)

  owner = payload&.dig('installation', 'account', 'login')
  return unless owner

  invalidate_all_scopes_for_owner(owner: owner)
end

#parse_event(payload:, event_type:, delivery_id:) ⇒ Object



26
27
28
29
# File 'lib/legion/extensions/github/app/runners/webhooks.rb', line 26

def parse_event(payload:, event_type:, delivery_id:, **)
  parsed = payload.is_a?(String) ? ::JSON.parse(payload) : payload
  { result: { event_type: event_type, delivery_id: delivery_id, payload: parsed } }
end

#receive_event(payload:, signature:, secret:, event_type:, delivery_id:) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/legion/extensions/github/app/runners/webhooks.rb', line 31

def receive_event(payload:, signature:, secret:, event_type:, delivery_id:, **)
  verified = verify_signature(payload: payload, signature: signature, secret: secret)[:result]
  unless verified
    return { result: { verified: false, event_type: event_type, delivery_id: delivery_id,
                       payload: nil } }
  end

  parsed = parse_event(payload: payload, event_type: event_type, delivery_id: delivery_id)[:result]
  invalidate_scopes_for_event(event_type: event_type, payload: parsed[:payload])
  { result: parsed.merge(verified: true) }
end

#verify_signature(payload:, signature:, secret:) ⇒ Object



15
16
17
18
19
20
21
22
23
24
# File 'lib/legion/extensions/github/app/runners/webhooks.rb', line 15

def verify_signature(payload:, signature:, secret:, **)
  return { result: false } if signature.nil? || signature.empty?

  expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret, payload)}"
  # Use constant-time comparison to prevent timing side-channel attacks.
  # Pad to equal length so fixed_length_secure_compare can be used safely.
  result = expected.length == signature.length &&
           OpenSSL.fixed_length_secure_compare(expected, signature)
  { result: result }
end