git_installation.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. )
  17. type GitInstallationScopedFactory struct {
  18. config *config.Config
  19. }
  20. func NewGitInstallationScopedFactory(
  21. config *config.Config,
  22. ) *GitInstallationScopedFactory {
  23. return &GitInstallationScopedFactory{config}
  24. }
  25. func (p *GitInstallationScopedFactory) Middleware(next http.Handler) http.Handler {
  26. return &GitInstallationScopedMiddleware{next, p.config}
  27. }
  28. type GitInstallationScopedMiddleware struct {
  29. next http.Handler
  30. config *config.Config
  31. }
  32. func (p *GitInstallationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  33. // read the user to perform authorization
  34. user, _ := r.Context().Value(types.UserScope).(*models.User)
  35. // get the registry id from the URL param context
  36. reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
  37. gitInstallationID := reqScopes[types.GitInstallationScope].Resource.UInt
  38. gitInstallation, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallationByInstallationID(gitInstallationID)
  39. if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
  40. apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrForbidden(err), true)
  41. return
  42. } else if err != nil {
  43. apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
  44. return
  45. }
  46. if err := p.doesUserHaveGitInstallationAccess(user.GithubAppIntegrationID, gitInstallationID); err != nil {
  47. apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
  48. return
  49. }
  50. ctx := NewGitInstallationContext(r.Context(), gitInstallation)
  51. r = r.Clone(ctx)
  52. p.next.ServeHTTP(w, r)
  53. }
  54. func NewGitInstallationContext(ctx context.Context, ga *integrations.GithubAppInstallation) context.Context {
  55. return context.WithValue(ctx, types.GitInstallationScope, ga)
  56. }
  57. // DoesUserHaveGitInstallationAccess checks that a user has access to an installation id
  58. // by ensuring the installation id exists for one org or account they have access to
  59. // note that this makes a github API request, but the endpoint is fast so this doesn't add
  60. // much overhead
  61. func (p *GitInstallationScopedMiddleware) doesUserHaveGitInstallationAccess(githubIntegrationID, gitInstallationID uint) error {
  62. oauthInt, err := p.config.Repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(githubIntegrationID)
  63. if err != nil {
  64. return err
  65. }
  66. if p.config.GithubAppConf == nil {
  67. return fmt.Errorf("config has invalid GithubAppConf")
  68. }
  69. if _, _, err = oauth.GetAccessToken(oauthInt.SharedOAuthModel,
  70. &p.config.GithubAppConf.Config,
  71. oauth.MakeUpdateGithubAppOauthIntegrationFunction(oauthInt, p.config.Repo)); err != nil {
  72. return err
  73. }
  74. client := github.NewClient(p.config.GithubConf.Client(oauth2.NoContext, &oauth2.Token{
  75. AccessToken: string(oauthInt.AccessToken),
  76. RefreshToken: string(oauthInt.RefreshToken),
  77. TokenType: "Bearer",
  78. }))
  79. accountIDs := make([]int64, 0)
  80. AuthUser, _, err := client.Users.Get(context.Background(), "")
  81. if err != nil {
  82. return err
  83. }
  84. accountIDs = append(accountIDs, *AuthUser.ID)
  85. opts := &github.ListOptions{
  86. PerPage: 100,
  87. Page: 1,
  88. }
  89. for {
  90. orgs, pages, err := client.Organizations.List(context.Background(), "", opts)
  91. if err != nil {
  92. return err
  93. }
  94. for _, org := range orgs {
  95. accountIDs = append(accountIDs, *org.ID)
  96. }
  97. if pages.NextPage == 0 {
  98. break
  99. }
  100. }
  101. installations, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallationByAccountIDs(accountIDs)
  102. for _, installation := range installations {
  103. if uint(installation.InstallationID) == gitInstallationID {
  104. return nil
  105. }
  106. }
  107. return apierrors.NewErrForbidden(
  108. fmt.Errorf("user does not have access to github app installation %d", gitInstallationID),
  109. )
  110. }