Skip to content

Commit

Permalink
CR-15777 (#666)
Browse files Browse the repository at this point in the history
* check token type (project is invalid)

* bump

* lint

* fix-test

* use autopilot v0.4.10

* resolve comments

* wip

* wip
  • Loading branch information
kim-codefresh authored Jan 11, 2023
1 parent 9161d8d commit d333159
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION=v0.1.26
VERSION=v0.1.27

OUT_DIR=dist
YEAR?=$(shell date +"%Y")
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
47 changes: 47 additions & 0 deletions pkg/git/provider_gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -32,6 +35,11 @@ type (
apiURL *url.URL
c *http.Client
}

gitlabUserResponse struct {
Username string `json:"username"`
Bot bool `json:"bot"`
}
)

const (
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
53 changes: 52 additions & 1 deletion pkg/git/provider_gitlab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -38,22 +41,70 @@ 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"))
},
},
"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)
},
},
"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())
Expand Down

0 comments on commit d333159

Please sign in to comment.