@@ -5,6 +5,8 @@ package cmd
55
66import (
77 "context"
8+ "crypto/hmac"
9+ "crypto/sha1" // #nosec G505 -- legacy Python compatibility: GitHub X-Hub-Signature uses SHA1
810 "encoding/json"
911 "errors"
1012 "fmt"
@@ -578,6 +580,8 @@ func server(localMode bool) http.Handler {
578580 configFile .AllowedOrigins )
579581 }
580582
583+ apiHandler = legacyGitHubInternalTriggerHandler (apiHandler , v1SignaturesService )
584+
581585 // OTel/Datadog (OTLP -> Datadog Lambda Extension) - enabled by flag
582586 if otelDatadogEnabled {
583587 apiHandler = telemetry .WrapHTTPHandler (apiHandler )
@@ -634,6 +638,164 @@ func setupCORSHandler(handler http.Handler, allowedOrigins []string) http.Handle
634638 return c .Handler (handler )
635639}
636640
641+ type legacyGitHubTriggerService interface {
642+ UpdateGitHubChangeRequest (ctx context.Context , installationID , repositoryID , pullRequestID int64 ) error
643+ UpdateGitHubMergeGroup (ctx context.Context , installationID , repositoryID int64 , mergeGroupSHA string , pullRequestID int64 ) error
644+ }
645+
646+ func legacyGitHubInternalTriggerHandler (next http.Handler , signatureService signatures.SignatureService ) http.Handler {
647+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
648+ if ! isLegacyGitHubInternalTriggerRequest (r ) {
649+ next .ServeHTTP (w , r )
650+ return
651+ }
652+ if r .Method != http .MethodPost {
653+ w .WriteHeader (http .StatusMethodNotAllowed )
654+ return
655+ }
656+ if signatureService == nil {
657+ writeLegacyGitHubTriggerJSON (w , http .StatusInternalServerError , map [string ]string {"error" : "signature service is not configured" })
658+ return
659+ }
660+ triggerService , ok := signatureService .(legacyGitHubTriggerService )
661+ if ! ok {
662+ writeLegacyGitHubTriggerJSON (w , http .StatusInternalServerError , map [string ]string {"error" : "signature service does not support legacy github trigger updates" })
663+ return
664+ }
665+ rawBody , err := io .ReadAll (io .LimitReader (r .Body , 1 << 20 ))
666+ if err != nil {
667+ writeLegacyGitHubTriggerJSON (w , http .StatusBadRequest , map [string ]string {"error" : "invalid body" })
668+ return
669+ }
670+ if sigStatus , sigErr := validateLegacyGitHubInternalTriggerSignature (rawBody , r .Header .Get ("X-Hub-Signature" )); sigErr != nil {
671+ writeLegacyGitHubTriggerJSON (w , sigStatus , map [string ]string {"error" : sigErr .Error ()})
672+ return
673+ }
674+
675+ var payload map [string ]any
676+ if unmarshalErr := json .Unmarshal (rawBody , & payload ); unmarshalErr != nil {
677+ writeLegacyGitHubTriggerJSON (w , http .StatusBadRequest , map [string ]string {"error" : "invalid json" })
678+ return
679+ }
680+
681+ installationID , err := legacyGitHubTriggerInt64 (payload , "installation_id" )
682+ if err != nil {
683+ writeLegacyGitHubTriggerJSON (w , http .StatusBadRequest , map [string ]string {"error" : err .Error ()})
684+ return
685+ }
686+ repositoryID , err := legacyGitHubTriggerInt64 (payload , "github_repository_id" )
687+ if err != nil {
688+ writeLegacyGitHubTriggerJSON (w , http .StatusBadRequest , map [string ]string {"error" : err .Error ()})
689+ return
690+ }
691+ changeRequestID , err := legacyGitHubTriggerInt64 (payload , "change_request_id" )
692+ if err != nil {
693+ writeLegacyGitHubTriggerJSON (w , http .StatusBadRequest , map [string ]string {"error" : err .Error ()})
694+ return
695+ }
696+
697+ mergeGroupSHA := legacyGitHubTriggerString (payload , "merge_group_sha" )
698+ if mergeGroupSHA != "" {
699+ if err := triggerService .UpdateGitHubMergeGroup (r .Context (), installationID , repositoryID , mergeGroupSHA , changeRequestID ); err != nil {
700+ writeLegacyGitHubTriggerJSON (w , http .StatusInternalServerError , map [string ]string {"error" : err .Error ()})
701+ return
702+ }
703+ writeLegacyGitHubTriggerJSON (w , http .StatusOK , map [string ]string {"status" : "OK" })
704+ return
705+ }
706+
707+ if err := triggerService .UpdateGitHubChangeRequest (r .Context (), installationID , repositoryID , changeRequestID ); err != nil {
708+ writeLegacyGitHubTriggerJSON (w , http .StatusInternalServerError , map [string ]string {"error" : err .Error ()})
709+ return
710+ }
711+ writeLegacyGitHubTriggerJSON (w , http .StatusOK , map [string ]string {"status" : "OK" })
712+ })
713+ }
714+
715+ func isLegacyGitHubInternalTriggerRequest (r * http.Request ) bool {
716+ if r == nil || r .URL == nil {
717+ return false
718+ }
719+ return strings .HasSuffix (strings .TrimRight (r .URL .Path , "/" ), "/github/activity" ) &&
720+ r .URL .Query ().Get ("legacy_internal_trigger" ) == "github-change-request"
721+ }
722+
723+ func validateLegacyGitHubInternalTriggerSignature (payload []byte , signatureHeader string ) (int , error ) {
724+ secret := strings .TrimSpace (os .Getenv ("GITHUB_APP_WEBHOOK_SECRET" ))
725+ if secret == "" {
726+ secret = strings .TrimSpace (os .Getenv ("GH_APP_WEBHOOK_SECRET" ))
727+ }
728+ if secret == "" {
729+ return http .StatusInternalServerError , errors .New ("GITHUB_APP_WEBHOOK_SECRET is empty" )
730+ }
731+
732+ signatureHeader = strings .TrimSpace (signatureHeader )
733+ if signatureHeader == "" {
734+ return http .StatusUnauthorized , errors .New ("missing X-Hub-Signature" )
735+ }
736+ parts := strings .SplitN (signatureHeader , "=" , 2 )
737+ if len (parts ) != 2 || parts [0 ] != "sha1" {
738+ return http .StatusUnauthorized , errors .New ("invalid X-Hub-Signature" )
739+ }
740+
741+ mac := hmac .New (sha1 .New , []byte (secret ))
742+ _ , _ = mac .Write (payload )
743+ expected := fmt .Sprintf ("%x" , mac .Sum (nil ))
744+ if ! hmac .Equal ([]byte (expected ), []byte (strings .TrimSpace (parts [1 ]))) {
745+ return http .StatusUnauthorized , errors .New ("invalid X-Hub-Signature" )
746+ }
747+ return 0 , nil
748+ }
749+
750+ func legacyGitHubTriggerString (payload map [string ]any , key string ) string {
751+ v , ok := payload [key ]
752+ if ! ok || v == nil {
753+ return ""
754+ }
755+ return strings .TrimSpace (fmt .Sprint (v ))
756+ }
757+
758+ func legacyGitHubTriggerInt64 (payload map [string ]any , key string ) (int64 , error ) {
759+ v , ok := payload [key ]
760+ if ! ok || v == nil {
761+ return 0 , fmt .Errorf ("missing %s" , key )
762+ }
763+ switch tv := v .(type ) {
764+ case string :
765+ i , err := strconv .ParseInt (strings .TrimSpace (tv ), 10 , 64 )
766+ if err != nil {
767+ return 0 , fmt .Errorf ("invalid %s" , key )
768+ }
769+ return i , nil
770+ case float64 :
771+ if tv != float64 (int64 (tv )) {
772+ return 0 , fmt .Errorf ("invalid %s" , key )
773+ }
774+ return int64 (tv ), nil
775+ case int64 :
776+ return tv , nil
777+ case int :
778+ return int64 (tv ), nil
779+ default :
780+ i , err := strconv .ParseInt (strings .TrimSpace (fmt .Sprint (tv )), 10 , 64 )
781+ if err != nil {
782+ return 0 , fmt .Errorf ("invalid %s" , key )
783+ }
784+ return i , nil
785+ }
786+ }
787+
788+ func writeLegacyGitHubTriggerJSON (w http.ResponseWriter , status int , payload any ) {
789+ w .Header ().Set ("Content-Type" , "application/json" )
790+ w .WriteHeader (status )
791+ if err := json .NewEncoder (w ).Encode (payload ); err != nil {
792+ log .WithFields (logrus.Fields {
793+ "functionName" : "cmd.writeLegacyGitHubTriggerJSON" ,
794+ "status" : status ,
795+ }).WithError (err ).Warn ("unable to encode legacy github trigger response" )
796+ }
797+ }
798+
637799// wrapHandlers routes the request to the appropriate handler
638800func wrapHandlers (v1 http.Handler , v1BasePath string , v2 http.Handler , v2BasePath string ) http.Handler {
639801 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
0 commit comments