@@ -16,8 +16,17 @@ package git
1616
1717import (
1818 "context"
19+ "errors"
20+ "fmt"
1921 "net/http"
2022 "net/url"
23+ "path"
24+ "strings"
25+
26+ "encoding/base64"
27+
28+ apgit "github.com/argoproj-labs/argocd-autopilot/pkg/git"
29+ httputil "github.com/codefresh-io/cli-v2/pkg/util/http"
2130)
2231
2332type (
@@ -30,10 +39,25 @@ type (
3039
3140const (
3241 BITBUCKET_CLOUD_DOMAIN = "bitbucket.org"
33- BITBUCKET_REST_ENDPOINT = "/2.0"
42+ BITBUCKET_REST_ENDPOINT = "/api/ 2.0"
3443 BITBUCKET ProviderType = "bitbucket"
3544)
3645
46+ var (
47+ patScopes = [][]string {
48+ {"repository:admin" , "repository:write" },
49+ {"account:read" , "account:write" },
50+ {"team" , "team:write" },
51+ }
52+
53+ runtimeScopes = [][]string {
54+ {"repository:admin" },
55+ {"account:read" , "account:write" },
56+ {"team" , "team:write" },
57+ {"webhook" },
58+ }
59+ )
60+
3761func NewBitbucketProvider (baseURL string , client * http.Client ) (Provider , error ) {
3862 u , err := url .Parse (baseURL )
3963 if err != nil {
@@ -63,34 +87,70 @@ func (bb *bitbucket) Type() ProviderType {
6387 return bb .providerType
6488}
6589
66- func (bb * bitbucket ) VerifyRuntimeToken (ctx context.Context , token string ) error {
67- return bb .checkProjectAdminPermission (ctx , token )
90+ func (bb * bitbucket ) VerifyRuntimeToken (ctx context.Context , auth apgit.Auth ) error {
91+ if auth .Password == "" {
92+ return fmt .Errorf ("user name is require for bitbucket cloud request" )
93+ }
94+
95+ return bb .verifyToken (ctx , auth .Password , auth .Username , runtimeScopes )
6896}
6997
70- func (bb * bitbucket ) VerifyUserToken (ctx context.Context , token string ) error {
71- return bb .checkRepoReadPermission (ctx , token )
98+ func (bb * bitbucket ) VerifyUserToken (ctx context.Context , auth apgit.Auth ) error {
99+ if auth .Password == "" {
100+ return fmt .Errorf ("user name is require for bitbucket cloud request" )
101+ }
102+ return bb .verifyToken (ctx , auth .Password , auth .Username , patScopes )
72103}
73104
74- func (bb * bitbucket ) checkProjectAdminPermission (ctx context.Context , token string ) error {
105+ func (bb * bitbucket ) verifyToken (ctx context.Context , token string , username string , requiredScopes [][]string ) error {
106+ scopes , err := bb .getCurrentUserScopes (ctx , token , username )
107+ if err != nil {
108+ return fmt .Errorf ("failed checking token scope permission: %w" , err )
109+ }
110+ for _ , requiredScope := range requiredScopes {
111+ isScopeIncluded := false
112+ for _ , scopeOpt := range requiredScope {
113+ if strings .Contains (scopes , scopeOpt ) {
114+ isScopeIncluded = true
115+ }
116+ }
117+ if ! isScopeIncluded {
118+ return fmt .Errorf ("the provided token is missing required token scopes, got: %s required: %v" , scopes , requiredScopes )
119+ }
120+ }
121+
75122 return nil
76123}
77124
78- func (bb * bitbucket ) checkRepoReadPermission (ctx context.Context , token string ) error {
79- return nil
125+ func (bb * bitbucket ) getCurrentUserScopes (ctx context.Context , token , username string ) (string , error ) {
126+ res , err := bb .request (ctx , username , token , http .MethodHead , "user" , nil )
127+ if err != nil {
128+ return "" , fmt .Errorf ("failed getting current user: %w" , err )
129+ }
130+ defer res .Body .Close ()
131+
132+ scopes := res .Header .Get ("x-oauth-scopes" )
133+
134+ if scopes == "" {
135+ return "" , errors .New ("invalid token" )
136+ }
137+
138+ return scopes , nil
80139}
81140
82- // func (bb *bitbucket) request(ctx context.Context, token, method, urlPath string, body interface{}) (*http.Response, error) {
83- // urlClone := *bb.apiURL
84- // urlClone.Path = path.Join(urlClone.Path, urlPath)
85- // headers := map[string]string{
86- // "Authorization": "Bearer " + token,
87- // "Accept": "application/json",
88- // "Content-Type": "application/json",
89- // }
90- // req, err := httputil.NewRequest(ctx, method, urlClone.String(), headers, body)
91- // if err != nil {
92- // return nil, err
93- // }
94-
95- // return bb.c.Do(req)
96- // }
141+ func (bb * bitbucket ) request (ctx context.Context , username , token , method , urlPath string , body interface {}) (* http.Response , error ) {
142+ urlClone := * bb .apiURL
143+ urlClone .Path = path .Join (urlClone .Path , urlPath )
144+ auth := base64 .StdEncoding .EncodeToString ([]byte (username + ":" + token ))
145+ headers := map [string ]string {
146+ "Authorization" : "Basic " + auth ,
147+ "Accept" : "application/json" ,
148+ "Content-Type" : "application/json" ,
149+ }
150+ req , err := httputil .NewRequest (ctx , method , urlClone .String (), headers , body )
151+ if err != nil {
152+ return nil , err
153+ }
154+
155+ return bb .c .Do (req )
156+ }
0 commit comments