// Copyright 2016 The go-github AUTHORS. All rights reserved.//// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// This file provides functions for validating payloads from GitHub Webhooks.// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-githubpackage githubimport ()const (// sha1Prefix is the prefix used by GitHub before the HMAC hexdigest. sha1Prefix = "sha1"// sha256Prefix and sha512Prefix are provided for future compatibility. sha256Prefix = "sha256" sha512Prefix = "sha512"// SHA1SignatureHeader is the GitHub header key used to pass the HMAC-SHA1 hexdigest.SHA1SignatureHeader = "X-Hub-Signature"// SHA256SignatureHeader is the GitHub header key used to pass the HMAC-SHA256 hexdigest.SHA256SignatureHeader = "X-Hub-Signature-256"// EventTypeHeader is the GitHub header key used to pass the event type.EventTypeHeader = "X-Github-Event"// DeliveryIDHeader is the GitHub header key used to pass the unique ID for the webhook event.DeliveryIDHeader = "X-Github-Delivery")var (// eventTypeMapping maps webhooks types to their corresponding go-github struct types. eventTypeMapping = map[string]interface{}{"branch_protection_rule": &BranchProtectionRuleEvent{},"check_run": &CheckRunEvent{},"check_suite": &CheckSuiteEvent{},"code_scanning_alert": &CodeScanningAlertEvent{},"commit_comment": &CommitCommentEvent{},"content_reference": &ContentReferenceEvent{},"create": &CreateEvent{},"delete": &DeleteEvent{},"dependabot_alert": &DependabotAlertEvent{},"deploy_key": &DeployKeyEvent{},"deployment": &DeploymentEvent{},"deployment_review": &DeploymentReviewEvent{},"deployment_status": &DeploymentStatusEvent{},"deployment_protection_rule": &DeploymentProtectionRuleEvent{},"discussion": &DiscussionEvent{},"discussion_comment": &DiscussionCommentEvent{},"fork": &ForkEvent{},"github_app_authorization": &GitHubAppAuthorizationEvent{},"gollum": &GollumEvent{},"installation": &InstallationEvent{},"installation_repositories": &InstallationRepositoriesEvent{},"installation_target": &InstallationTargetEvent{},"issue_comment": &IssueCommentEvent{},"issues": &IssuesEvent{},"label": &LabelEvent{},"marketplace_purchase": &MarketplacePurchaseEvent{},"member": &MemberEvent{},"membership": &MembershipEvent{},"merge_group": &MergeGroupEvent{},"meta": &MetaEvent{},"milestone": &MilestoneEvent{},"organization": &OrganizationEvent{},"org_block": &OrgBlockEvent{},"package": &PackageEvent{},"page_build": &PageBuildEvent{},"personal_access_token_request": &PersonalAccessTokenRequestEvent{},"ping": &PingEvent{},"project": &ProjectEvent{},"project_card": &ProjectCardEvent{},"project_column": &ProjectColumnEvent{},"projects_v2": &ProjectV2Event{},"projects_v2_item": &ProjectV2ItemEvent{},"public": &PublicEvent{},"pull_request": &PullRequestEvent{},"pull_request_review": &PullRequestReviewEvent{},"pull_request_review_comment": &PullRequestReviewCommentEvent{},"pull_request_review_thread": &PullRequestReviewThreadEvent{},"pull_request_target": &PullRequestTargetEvent{},"push": &PushEvent{},"repository": &RepositoryEvent{},"repository_dispatch": &RepositoryDispatchEvent{},"repository_import": &RepositoryImportEvent{},"repository_vulnerability_alert": &RepositoryVulnerabilityAlertEvent{},"release": &ReleaseEvent{},"secret_scanning_alert": &SecretScanningAlertEvent{},"security_advisory": &SecurityAdvisoryEvent{},"security_and_analysis": &SecurityAndAnalysisEvent{},"sponsorship": &SponsorshipEvent{},"star": &StarEvent{},"status": &StatusEvent{},"team": &TeamEvent{},"team_add": &TeamAddEvent{},"user": &UserEvent{},"watch": &WatchEvent{},"workflow_dispatch": &WorkflowDispatchEvent{},"workflow_job": &WorkflowJobEvent{},"workflow_run": &WorkflowRunEvent{}, }// forward mapping of event types to the string names of the structs messageToTypeName = make(map[string]string, len(eventTypeMapping))// Inverse map of the above typeToMessageMapping = make(map[string]string, len(eventTypeMapping)))func init() {for , := rangeeventTypeMapping { := reflect.TypeOf().Elem().Name()messageToTypeName[] = typeToMessageMapping[] = }}// genMAC generates the HMAC signature for a message provided the secret key// and hashFunc.func genMAC(, []byte, func() hash.Hash) []byte { := hmac.New(, ) .Write()return .Sum(nil)}// checkMAC reports whether messageMAC is a valid HMAC tag for message.func checkMAC(, , []byte, func() hash.Hash) bool { := genMAC(, , )returnhmac.Equal(, )}// messageMAC returns the hex-decoded HMAC tag from the signature and its// corresponding hash function.func messageMAC( string) ([]byte, func() hash.Hash, error) {if == "" {returnnil, nil, errors.New("missing signature") } := strings.SplitN(, "=", 2)iflen() != 2 {returnnil, nil, fmt.Errorf("error parsing signature %q", ) }varfunc() hash.Hashswitch [0] {casesha1Prefix: = sha1.Newcasesha256Prefix: = sha256.Newcasesha512Prefix: = sha512.Newdefault:returnnil, nil, fmt.Errorf("unknown hash type prefix: %q", [0]) } , := hex.DecodeString([1])if != nil {returnnil, nil, fmt.Errorf("error decoding signature %q: %v", , ) }return , , nil}// ValidatePayloadFromBody validates an incoming GitHub Webhook event request body// and returns the (JSON) payload.// The Content-Type header of the payload can be "application/json" or "application/x-www-form-urlencoded".// If the Content-Type is neither then an error is returned.// secretToken is the GitHub Webhook secret token.// If your webhook does not contain a secret token, you can pass an empty secretToken.// Webhooks without a secret token are not secure and should be avoided.//// Example usage://// func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {// // read signature from request// signature := ""// payload, err := github.ValidatePayloadFromBody(r.Header.Get("Content-Type"), r.Body, signature, s.webhookSecretKey)// if err != nil { ... }// // Process payload...// }func ( string, io.Reader, string, []byte) ( []byte, error) {var []byte// Raw body that GitHub uses to calculate the signature.switch {case"application/json":varerrorif , = io.ReadAll(); != nil {returnnil, }// If the content type is application/json, // the JSON payload is just the original body. = case"application/x-www-form-urlencoded":// payloadFormParam is the name of the form parameter that the JSON payload // will be in if a webhook has its content type set to application/x-www-form-urlencoded.const = "payload"varerrorif , = io.ReadAll(); != nil {returnnil, }// If the content type is application/x-www-form-urlencoded, // the JSON payload will be under the "payload" form param. , := url.ParseQuery(string())if != nil {returnnil, } = []byte(.Get())default:returnnil, fmt.Errorf("webhook request has unsupported Content-Type %q", ) }// Validate the signature if present or if one is expected (secretToken is non-empty).iflen() > 0 || len() > 0 {if := ValidateSignature(, , ); != nil {returnnil, } }return , nil}// ValidatePayload validates an incoming GitHub Webhook event request// and returns the (JSON) payload.// The Content-Type header of the payload can be "application/json" or "application/x-www-form-urlencoded".// If the Content-Type is neither then an error is returned.// secretToken is the GitHub Webhook secret token.// If your webhook does not contain a secret token, you can pass nil or an empty slice.// This is intended for local development purposes only and all webhooks should ideally set up a secret token.//// Example usage://// func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {// payload, err := github.ValidatePayload(r, s.webhookSecretKey)// if err != nil { ... }// // Process payload...// }func ( *http.Request, []byte) ( []byte, error) { := .Header.Get(SHA256SignatureHeader)if == "" { = .Header.Get(SHA1SignatureHeader) } , , := mime.ParseMediaType(.Header.Get("Content-Type"))if != nil {returnnil, }returnValidatePayloadFromBody(, .Body, , )}// ValidateSignature validates the signature for the given payload.// signature is the GitHub hash signature delivered in the X-Hub-Signature header.// payload is the JSON payload sent by GitHub Webhooks.// secretToken is the GitHub Webhook secret token.//// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-githubfunc ( string, , []byte) error { , , := messageMAC()if != nil {return }if !checkMAC(, , , ) {returnerrors.New("payload signature check failed") }returnnil}// WebHookType returns the event type of webhook request r.//// GitHub API docs: https://docs.github.com/developers/webhooks-and-events/events/github-event-typesfunc ( *http.Request) string {return .Header.Get(EventTypeHeader)}// DeliveryID returns the unique delivery ID of webhook request r.//// GitHub API docs: https://docs.github.com/developers/webhooks-and-events/events/github-event-typesfunc ( *http.Request) string {return .Header.Get(DeliveryIDHeader)}// ParseWebHook parses the event payload. For recognized event types, a// value of the corresponding struct type will be returned (as returned// by Event.ParsePayload()). An error will be returned for unrecognized event// types.//// Example usage://// func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {// payload, err := github.ValidatePayload(r, s.webhookSecretKey)// if err != nil { ... }// event, err := github.ParseWebHook(github.WebHookType(r), payload)// if err != nil { ... }// switch event := event.(type) {// case *github.CommitCommentEvent:// processCommitCommentEvent(event)// case *github.CreateEvent:// processCreateEvent(event)// ...// }// }func ( string, []byte) (interface{}, error) { , := messageToTypeName[]if ! {returnnil, fmt.Errorf("unknown X-Github-Event in message: %v", ) } := Event{Type: &,RawPayload: (*json.RawMessage)(&), }return .ParsePayload()}// MessageTypes returns a sorted list of all the known GitHub event type strings// supported by go-github.func () []string { := make([]string, 0, len(eventTypeMapping))for := rangeeventTypeMapping { = append(, ) }sort.Strings()return}// EventForType returns an empty struct matching the specified GitHub event type.// If messageType does not match any known event types, it returns nil.func ( string) interface{} { := eventTypeMapping[]if == nil {returnnil }// return a _copy_ of the pointed-to-object. Unfortunately, for this we // need to use reflection. If we store the actual objects in the map, // we still need to use reflection to convert from `any` to the actual // type, so this was deemed the lesser of two evils. (#2865)returnreflect.New(reflect.TypeOf().Elem()).Interface()}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.