@@ -16,8 +16,17 @@ package git
16
16
17
17
import (
18
18
"context"
19
+ "errors"
20
+ "fmt"
19
21
"net/http"
20
22
"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"
21
30
)
22
31
23
32
type (
@@ -30,10 +39,25 @@ type (
30
39
31
40
const (
32
41
BITBUCKET_CLOUD_DOMAIN = "bitbucket.org"
33
- BITBUCKET_REST_ENDPOINT = "/2.0"
42
+ BITBUCKET_REST_ENDPOINT = "/api/ 2.0"
34
43
BITBUCKET ProviderType = "bitbucket"
35
44
)
36
45
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
+
37
61
func NewBitbucketProvider (baseURL string , client * http.Client ) (Provider , error ) {
38
62
u , err := url .Parse (baseURL )
39
63
if err != nil {
@@ -63,34 +87,70 @@ func (bb *bitbucket) Type() ProviderType {
63
87
return bb .providerType
64
88
}
65
89
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 )
68
96
}
69
97
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 )
72
103
}
73
104
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
+
75
122
return nil
76
123
}
77
124
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
80
139
}
81
140
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