diff --git a/Makefile b/Makefile index edf947db..5341bc9c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=v0.1.26 +VERSION=v0.1.27 OUT_DIR=dist YEAR?=$(shell date +"%Y") diff --git a/go.mod b/go.mod index 657f6d40..4cd5ea52 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/Masterminds/semver/v3 v3.1.1 - github.com/argoproj-labs/argocd-autopilot v0.4.9 + github.com/argoproj-labs/argocd-autopilot v0.4.10 github.com/argoproj/argo-cd/v2 v2.5.2 github.com/argoproj/argo-events v0.17.1-0.20220327045437-70eaafe9afec github.com/argoproj/argo-workflows/v3 v3.3.1 diff --git a/go.sum b/go.sum index 8430e115..8828f7ac 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc= -github.com/argoproj-labs/argocd-autopilot v0.4.9 h1:mXptimJTxZhpgq2lRZTQLO4OHyGyNZpsIxYPdWmdnOY= -github.com/argoproj-labs/argocd-autopilot v0.4.9/go.mod h1:iR9JosRv7UBOQBSDR64Rrg1qs1DURhR+kx8zK2GEap4= +github.com/argoproj-labs/argocd-autopilot v0.4.10 h1:0I0U28wvwoZkh2/ECKEgMDFw71Dzh6WCSAU4TkWuxY8= +github.com/argoproj-labs/argocd-autopilot v0.4.10/go.mod h1:iR9JosRv7UBOQBSDR64Rrg1qs1DURhR+kx8zK2GEap4= github.com/argoproj/argo-cd/v2 v2.5.2 h1:hyPi8NFXW3tG2yURslIMI20GfCdTN1/BDnt4+v5lpoA= github.com/argoproj/argo-cd/v2 v2.5.2/go.mod h1:3ToENm286PFVlZKNMutBzOwNyhevz4fw9dcgyiq3FIY= github.com/argoproj/argo-events v0.17.1-0.20220327045437-70eaafe9afec h1:95S2LPUUdPO2jYxuR5z1uk1GL2m/u+ud2iFAr5gK6VI= diff --git a/pkg/git/provider_gitlab.go b/pkg/git/provider_gitlab.go index 4b2d158d..757c2189 100644 --- a/pkg/git/provider_gitlab.go +++ b/pkg/git/provider_gitlab.go @@ -16,11 +16,14 @@ package git import ( "context" + "encoding/json" "errors" "fmt" + "io" "net/http" "net/url" "path" + "strings" apgit "github.com/argoproj-labs/argocd-autopilot/pkg/git" httputil "github.com/codefresh-io/cli-v2/pkg/util/http" @@ -32,6 +35,11 @@ type ( apiURL *url.URL c *http.Client } + + gitlabUserResponse struct { + Username string `json:"username"` + Bot bool `json:"bot"` + } ) const ( @@ -81,6 +89,16 @@ func (g *gitlab) VerifyUserToken(ctx context.Context, auth apgit.Auth) error { // if it returns 400 - the token has "api" scope // otherwise - the token does not have the scope func (g *gitlab) checkApiScope(ctx context.Context, token string) error { + + tokenType, err := g.checkTokenType(token, ctx) + if err != nil { + return fmt.Errorf("failed checking api scope: %w", err) + } + + if tokenType == "project" { + return errors.New("runtime git-token is invalid, project token is not exceptable") + } + res, err := g.request(ctx, token, http.MethodPost, "projects") if err != nil { return fmt.Errorf("failed checking api scope: %w", err) @@ -94,6 +112,35 @@ func (g *gitlab) checkApiScope(ctx context.Context, token string) error { return nil } +func (g *gitlab) checkTokenType(token string, ctx context.Context) (string, error) { + userRes, err := g.request(ctx, token, http.MethodGet, "user") + + if err != nil { + return "", fmt.Errorf("failed getting user: %w", err) + } + + defer userRes.Body.Close() + + bodyBytes, err := io.ReadAll(userRes.Body) + if err != nil { + return "", fmt.Errorf("failed reading user body: %w", err) + } + + var user gitlabUserResponse + err = json.Unmarshal(bodyBytes, &user) + if err != nil { + return "", fmt.Errorf("failed parse user body: %w", err) + } + if user.Bot { + if strings.HasPrefix(user.Username, "project") { + return "project", nil + } + return "group", nil + } + + return "personal", nil +} + // HEAD to projects. // if it returns 200 - the token has "repo_read" scope // otherwise - the token does not have the scope diff --git a/pkg/git/provider_gitlab_test.go b/pkg/git/provider_gitlab_test.go index 6c77ce4c..7f5f4cdf 100644 --- a/pkg/git/provider_gitlab_test.go +++ b/pkg/git/provider_gitlab_test.go @@ -16,8 +16,11 @@ package git import ( "context" + "encoding/json" "errors" + "io" "net/http" + "strings" "testing" "github.com/codefresh-io/cli-v2/pkg/git/mocks" @@ -38,8 +41,28 @@ func Test_gitlab_checkApiScope(t *testing.T) { wantErr string beforeFn func(t *testing.T, c *mocks.MockRoundTripper) }{ - "Should fail if POST fails": { + "Should fail if POST projects fails": { wantErr: "failed checking api scope: Post \"https://some.server/api/v4/projects\": some error", + beforeFn: func(_ *testing.T, c *mocks.MockRoundTripper) { + c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Times(1).DoAndReturn(func(req *http.Request) (*http.Response, error) { + assert.Equal(t, "GET", req.Method) + assert.Equal(t, "https://some.server/api/v4/user", req.URL.String()) + body, _ := json.Marshal(&gitlabUserResponse{ + Username: "username", + Bot: false, + }) + bodyReader := io.NopCloser(strings.NewReader(string(body[:]))) + res := &http.Response{ + StatusCode: 200, + Body: bodyReader, + } + return res, nil + }) + c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Return(nil, errors.New("some error")) + }, + }, + "Should fail if GET user fails": { + wantErr: "failed checking api scope: failed getting user: Get \"https://some.server/api/v4/user\": some error", beforeFn: func(_ *testing.T, c *mocks.MockRoundTripper) { c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Return(nil, errors.New("some error")) }, @@ -47,6 +70,20 @@ func Test_gitlab_checkApiScope(t *testing.T) { "Should fail if POST fails with 403": { wantErr: "git-token is invalid or missing required \"api\" scope", beforeFn: func(_ *testing.T, c *mocks.MockRoundTripper) { + c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Times(1).DoAndReturn(func(req *http.Request) (*http.Response, error) { + assert.Equal(t, "GET", req.Method) + assert.Equal(t, "https://some.server/api/v4/user", req.URL.String()) + body, _ := json.Marshal(&gitlabUserResponse{ + Username: "username", + Bot: false, + }) + bodyReader := io.NopCloser(strings.NewReader(string(body[:]))) + res := &http.Response{ + StatusCode: 200, + Body: bodyReader, + } + return res, nil + }) c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Times(1).Return(&http.Response{ StatusCode: http.StatusForbidden, }, nil) @@ -54,6 +91,20 @@ func Test_gitlab_checkApiScope(t *testing.T) { }, "Should succeed if POST returns 400": { beforeFn: func(t *testing.T, c *mocks.MockRoundTripper) { + c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Times(1).DoAndReturn(func(req *http.Request) (*http.Response, error) { + assert.Equal(t, "GET", req.Method) + assert.Equal(t, "https://some.server/api/v4/user", req.URL.String()) + body, _ := json.Marshal(&gitlabUserResponse{ + Username: "username", + Bot: false, + }) + bodyReader := io.NopCloser(strings.NewReader(string(body[:]))) + res := &http.Response{ + StatusCode: 200, + Body: bodyReader, + } + return res, nil + }) c.EXPECT().RoundTrip(gomock.AssignableToTypeOf(&http.Request{})).Times(1).DoAndReturn(func(req *http.Request) (*http.Response, error) { assert.Equal(t, "POST", req.Method) assert.Equal(t, "https://some.server/api/v4/projects", req.URL.String())