| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- package authz
- import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "github.com/google/go-github/v41/github"
- "golang.org/x/oauth2"
- "gorm.io/gorm"
- "github.com/porter-dev/porter/api/server/shared/apierrors"
- "github.com/porter-dev/porter/api/server/shared/config"
- "github.com/porter-dev/porter/api/types"
- "github.com/porter-dev/porter/internal/models"
- "github.com/porter-dev/porter/internal/models/integrations"
- "github.com/porter-dev/porter/internal/oauth"
- "github.com/porter-dev/porter/internal/telemetry"
- )
- type GitInstallationScopedFactory struct {
- config *config.Config
- }
- func NewGitInstallationScopedFactory(
- config *config.Config,
- ) *GitInstallationScopedFactory {
- return &GitInstallationScopedFactory{config}
- }
- func (p *GitInstallationScopedFactory) Middleware(next http.Handler) http.Handler {
- return &GitInstallationScopedMiddleware{next, p.config}
- }
- type GitInstallationScopedMiddleware struct {
- next http.Handler
- config *config.Config
- }
- func (p *GitInstallationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ctx, span := telemetry.NewSpan(r.Context(), "middleware-git-installation")
- defer span.End()
- // read the user to perform authorization
- user, _ := ctx.Value(types.UserScope).(*models.User)
- // get the registry id from the URL param context
- reqScopes, _ := ctx.Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
- gitInstallationID := reqScopes[types.GitInstallationScope].Resource.UInt
- telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "git-installation-id", Value: gitInstallationID})
- gitInstallation, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallationByInstallationID(gitInstallationID)
- if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
- err = telemetry.Error(ctx, span, err, "git installation not found")
- apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound), true)
- return
- } else if err != nil {
- err = telemetry.Error(ctx, span, err, "git installation not found")
- apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrPassThroughToClient(err, http.StatusNotFound), true)
- return
- }
- if err := p.doesUserHaveGitInstallationAccess(ctx, user.GithubAppIntegrationID, gitInstallationID); err != nil {
- err = telemetry.Error(ctx, span, err, "user does not have access to git installation")
- apierrors.HandleAPIError(p.config.Logger, p.config.Alerter, w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden), true)
- return
- }
- ctx = NewGitInstallationContext(ctx, gitInstallation)
- r = r.Clone(ctx)
- p.next.ServeHTTP(w, r)
- }
- func NewGitInstallationContext(ctx context.Context, ga *integrations.GithubAppInstallation) context.Context {
- return context.WithValue(ctx, types.GitInstallationScope, ga)
- }
- // DoesUserHaveGitInstallationAccess checks that a user has access to an installation id
- // by ensuring the installation id exists for one org or account they have access to
- // note that this makes a github API request, but the endpoint is fast so this doesn't add
- // much overhead
- func (p *GitInstallationScopedMiddleware) doesUserHaveGitInstallationAccess(ctx context.Context, githubIntegrationID, gitInstallationID uint) error {
- ctx, span := telemetry.NewSpan(ctx, "check-user-has-git-installation-access")
- defer span.End()
- oauthInt, err := p.config.Repo.GithubAppOAuthIntegration().ReadGithubAppOauthIntegration(githubIntegrationID)
- if err != nil {
- return telemetry.Error(ctx, span, err, "unable to read github app oauth integration")
- }
- if p.config.GithubAppConf == nil {
- return telemetry.Error(ctx, span, nil, "config has invalid GithubAppConf")
- }
- if _, _, err = oauth.GetAccessToken(oauthInt.SharedOAuthModel,
- &p.config.GithubAppConf.Config,
- oauth.MakeUpdateGithubAppOauthIntegrationFunction(oauthInt, p.config.Repo)); err != nil {
- return telemetry.Error(ctx, span, err, "unable to get access token")
- }
- client := github.NewClient(p.config.GithubConf.Client(ctx, &oauth2.Token{
- AccessToken: string(oauthInt.AccessToken),
- RefreshToken: string(oauthInt.RefreshToken),
- TokenType: "Bearer",
- }))
- accountIDs := make([]int64, 0)
- AuthUser, _, err := client.Users.Get(ctx, "")
- if err != nil {
- return telemetry.Error(ctx, span, err, "unable to get authenticated user")
- }
- accountIDs = append(accountIDs, *AuthUser.ID)
- opts := &github.ListOptions{
- PerPage: 100,
- Page: 1,
- }
- for {
- orgs, pages, err := client.Organizations.List(ctx, "", opts)
- if err != nil {
- return telemetry.Error(ctx, span, err, "unable to list organizations")
- }
- for _, org := range orgs {
- accountIDs = append(accountIDs, *org.ID)
- }
- if pages.NextPage == 0 {
- break
- }
- }
- telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "account-ids", Value: fmt.Sprintf("%v", accountIDs)})
- installations, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallationByAccountIDs(accountIDs)
- if err != nil {
- return telemetry.Error(ctx, span, err, "unable to read github app installations")
- }
- installationIds := make([]int64, 0)
- for _, installation := range installations {
- installationIds = append(installationIds, installation.InstallationID)
- }
- telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "installation-ids-for-account-ids", Value: fmt.Sprintf("%v", installationIds)})
- for _, installation := range installations {
- if uint(installation.InstallationID) == gitInstallationID {
- return nil
- }
- }
- return telemetry.Error(ctx, span, nil, "user does not have access to github app installation")
- }
|