2
0

git_installation.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package authz
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "github.com/google/go-github/v41/github"
  8. "golang.org/x/oauth2"
  9. "gorm.io/gorm"
  10. "github.com/porter-dev/porter/api/server/shared/apierrors"
  11. "github.com/porter-dev/porter/api/server/shared/config"
  12. "github.com/porter-dev/porter/api/types"
  13. "github.com/porter-dev/porter/internal/models"
  14. "github.com/porter-dev/porter/internal/models/integrations"
  15. "github.com/porter-dev/porter/internal/oauth"
  16. "github.com/porter-dev/porter/internal/telemetry"
  17. )
  18. type GitInstallationScopedFactory struct {
  19. config *config.Config
  20. }
  21. func NewGitInstallationScopedFactory(
  22. config *config.Config,
  23. ) *GitInstallationScopedFactory {
  24. return &GitInstallationScopedFactory{config}
  25. }
  26. func (p *GitInstallationScopedFactory) Middleware(next http.Handler) http.Handler {
  27. return &GitInstallationScopedMiddleware{next, p.config}
  28. }
  29. type GitInstallationScopedMiddleware struct {
  30. next http.Handler
  31. config *config.Config
  32. }
  33. func (p *GitInstallationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. ctx, span := telemetry.NewSpan(r.Context(), "middleware-git-installation")
  35. defer span.End()
  36. // read the user to perform authorization
  37. user, _ := ctx.Value(types.UserScope).(*models.User)
  38. // get the registry id from the URL param context
  39. reqScopes, _ := ctx.Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
  40. gitInstallationID := reqScopes[types.GitInstallationScope].Resource.UInt
  41. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "git-installation-id", Value: gitInstallationID})
  42. gitInstallation, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallationByInstallationID(gitInstallationID)
  43. if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
  44. err = telemetry.Error(ctx, span, err, "git installation not found")
  45. apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound), true)
  46. return
  47. } else if err != nil {
  48. err = telemetry.Error(ctx, span, err, "git installation not found")
  49. apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound), true)
  50. return
  51. }
  52. if err := p.doesUserHaveGitInstallationAccess(ctx, user.GithubAppIntegrationID, gitInstallationID); err != nil {
  53. err = telemetry.Error(ctx, span, err, "user does not have access to git installation")
  54. apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden), true)
  55. return
  56. }
  57. ctx = NewGitInstallationContext(ctx, gitInstallation)
  58. r = r.Clone(ctx)
  59. p.next.ServeHTTP(w, r)
  60. }
  61. func NewGitInstallationContext(ctx context.Context, ga *integrations.GithubAppInstallation) context.Context {
  62. return context.WithValue(ctx, types.GitInstallationScope, ga)
  63. }
  64. // DoesUserHaveGitInstallationAccess checks that a user has access to an installation id
  65. // by ensuring the installation id exists for one org or account they have access to
  66. // note that this makes a github API request, but the endpoint is fast so this doesn't add
  67. // much overhead
  68. func (p *GitInstallationScopedMiddleware) doesUserHaveGitInstallationAccess(ctx context.Context, githubIntegrationID, gitInstallationID uint) error {
  69. ctx, span := telemetry.NewSpan(ctx, "check-user-has-git-installation-access")
  70. defer span.End()
  71. oauthInt, err := p.config.Repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(githubIntegrationID)
  72. if err != nil {
  73. return telemetry.Error(ctx, span, err, "unable to read github app oauth integration")
  74. }
  75. if p.config.GithubAppConf == nil {
  76. return telemetry.Error(ctx, span, nil, "config has invalid GithubAppConf")
  77. }
  78. if _, _, err = oauth.GetAccessToken(oauthInt.SharedOAuthModel,
  79. &p.config.GithubAppConf.Config,
  80. oauth.MakeUpdateGithubAppOauthIntegrationFunction(oauthInt, p.config.Repo)); err != nil {
  81. return telemetry.Error(ctx, span, err, "unable to get access token")
  82. }
  83. client := github.NewClient(p.config.GithubConf.Client(ctx, &oauth2.Token{
  84. AccessToken: string(oauthInt.AccessToken),
  85. RefreshToken: string(oauthInt.RefreshToken),
  86. TokenType: "Bearer",
  87. }))
  88. accountIDs := make([]int64, 0)
  89. AuthUser, _, err := client.Users.Get(ctx, "")
  90. if err != nil {
  91. return telemetry.Error(ctx, span, err, "unable to get authenticated user")
  92. }
  93. accountIDs = append(accountIDs, *AuthUser.ID)
  94. opts := &github.ListOptions{
  95. PerPage: 100,
  96. Page: 1,
  97. }
  98. for {
  99. orgs, pages, err := client.Organizations.List(ctx, "", opts)
  100. if err != nil {
  101. return telemetry.Error(ctx, span, err, "unable to list organizations")
  102. }
  103. for _, org := range orgs {
  104. accountIDs = append(accountIDs, *org.ID)
  105. }
  106. if pages.NextPage == 0 {
  107. break
  108. }
  109. }
  110. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "account-ids", Value: fmt.Sprintf("%v", accountIDs)})
  111. installations, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallationByAccountIDs(accountIDs)
  112. if err != nil {
  113. return telemetry.Error(ctx, span, err, "unable to read github app installations")
  114. }
  115. installationIds := make([]int64, 0)
  116. for _, installation := range installations {
  117. installationIds = append(installationIds, installation.InstallationID)
  118. }
  119. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "installation-ids-for-account-ids", Value: fmt.Sprintf("%v", installationIds)})
  120. for _, installation := range installations {
  121. if uint(installation.InstallationID) == gitInstallationID {
  122. return nil
  123. }
  124. }
  125. return telemetry.Error(ctx, span, nil, "user does not have access to github app installation")
  126. }