Module: ActiveProject::Adapters::GithubRepo::Webhooks
- Included in:
- ActiveProject::Adapters::GithubRepoAdapter
- Defined in:
- lib/active_project/adapters/github_repo/webhooks.rb
Instance Method Summary collapse
-
#parse_webhook(request_body, headers = {}) ⇒ ActiveProject::WebhookEvent?
Parses an incoming webhook payload into a standardized WebhookEvent struct.
-
#secure_compare(a, b) ⇒ Boolean
Constant-time comparison to prevent timing attacks.
-
#verify_webhook_signature(request_body, signature_header) ⇒ Boolean
Validates incoming webhook signature using X-Hub-Signature-256 header.
Instance Method Details
#parse_webhook(request_body, headers = {}) ⇒ ActiveProject::WebhookEvent?
Parses an incoming webhook payload into a standardized WebhookEvent struct
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/active_project/adapters/github_repo/webhooks.rb', line 52 def parse_webhook(request_body, headers = {}) data = JSON.parse(request_body) event_type = headers["X-GitHub-Event"] case event_type when "issues" parse_issue_event(data) when "issue_comment" parse_comment_event(data) when "pull_request" parse_pull_request_event(data) else nil # Unsupported event type end rescue JSON::ParserError nil # Return nil for invalid JSON end |
#secure_compare(a, b) ⇒ Boolean
Constant-time comparison to prevent timing attacks
39 40 41 42 43 44 45 46 |
# File 'lib/active_project/adapters/github_repo/webhooks.rb', line 39 def secure_compare(a, b) return false if a.bytesize != b.bytesize l = a.unpack("C*") res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end |
#verify_webhook_signature(request_body, signature_header) ⇒ Boolean
Validates incoming webhook signature using X-Hub-Signature-256 header
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/active_project/adapters/github_repo/webhooks.rb', line 11 def verify_webhook_signature(request_body, signature_header) webhook_secret = @config.[:webhook_secret] # No webhook secret configured = no verification needed return true if webhook_secret.nil? || webhook_secret.empty? # Signature header is required when a secret is configured return false unless signature_header # GitHub uses 'sha256=' prefix for their signatures algorithm, signature = signature_header.split("=", 2) return false unless algorithm == "sha256" && signature # Calculate expected signature expected_signature = OpenSSL::HMAC.hexdigest( OpenSSL::Digest.new("sha256"), webhook_secret, request_body ) # Perform a secure comparison secure_compare(signature, expected_signature) end |