@@ -90,11 +90,7 @@ type OAuth2Config struct {
90
90
91
91
// codeGrantConfig creates an oauth2 config for refreshing
92
92
// and generating a token.
93
- func (c * OAuth2Config ) codeGrantConfig () * oauth2.Config {
94
- scopes := []string {"signature" }
95
- if c .ExtendedLifetime {
96
- scopes = []string {"signature" , "extended" }
97
- }
93
+ func (c * OAuth2Config ) codeGrantConfig (scopes ... string ) * oauth2.Config {
98
94
return & oauth2.Config {
99
95
RedirectURL : c .RedirURL ,
100
96
ClientID : c .IntegratorKey ,
@@ -105,14 +101,31 @@ func (c *OAuth2Config) codeGrantConfig() *oauth2.Config {
105
101
}
106
102
}
107
103
104
+ func addUnique (scopes []string , scope string ) []string {
105
+ for _ , val := range scopes {
106
+ if val == scope {
107
+ return scopes
108
+ }
109
+ }
110
+ return append (scopes , scope )
111
+ }
112
+
108
113
// AuthURL returns a URL to DocuSign's OAuth 2.0 consent page with
109
114
// all appropriate query parmeters for starting 3-legged OAuth2Flow.
110
115
//
116
+ // If scopes are empty, {"signature"} is assumed.
117
+ //
111
118
// State is a token to protect the user from CSRF attacks. You must
112
119
// always provide a non-zero string and validate that it matches the
113
120
// the state query parameter on your redirect callback.
114
- func (c * OAuth2Config ) AuthURL (state string ) string {
115
- cfg := c .codeGrantConfig () // client not needed for this action
121
+ func (c * OAuth2Config ) AuthURL (state string , scopes ... string ) string {
122
+ if len (scopes ) == 0 {
123
+ scopes = []string {"signature" }
124
+ }
125
+ if c .ExtendedLifetime {
126
+ scopes = addUnique (scopes , "extended" )
127
+ }
128
+ cfg := c .codeGrantConfig (scopes ... )
116
129
opts := make ([]oauth2.AuthCodeOption , 0 )
117
130
if c .Prompt {
118
131
opts = append (opts , oauth2 .SetAuthURLParam ("prompt" , "login" ))
@@ -133,7 +146,7 @@ func (c *OAuth2Config) AuthURL(state string) string {
133
146
// The code will be in the *http.Request.FormValue("code"). Before
134
147
// calling Exchange, be sure to validate FormValue("state").
135
148
func (c * OAuth2Config ) Exchange (ctx context.Context , code string ) (* OAuth2Credential , error ) {
136
- cfg := c .codeGrantConfig ()
149
+ cfg := c .codeGrantConfig () // scopes are not passed in this step
137
150
// oauth2 exchange
138
151
tk , err := cfg .Exchange (ctx , code )
139
152
if err != nil {
@@ -152,7 +165,7 @@ func (c *OAuth2Config) Exchange(ctx context.Context, code string) (*OAuth2Creden
152
165
}
153
166
154
167
func (c * OAuth2Config ) refresher () func (context.Context , * oauth2.Token ) (* oauth2.Token , error ) {
155
- cfg := c .codeGrantConfig ()
168
+ cfg := c .codeGrantConfig () // scopes are not passed in this step
156
169
return func (ctx context.Context , tk * oauth2.Token ) (* oauth2.Token , error ) {
157
170
if tk == nil || tk .RefreshToken == "" {
158
171
return nil , errors .New ("codeGrantRefresher: empty refresh token" )
@@ -224,24 +237,33 @@ type JWTConfig struct {
224
237
}
225
238
226
239
// UserConsentURL creates a url allowing a user to consent to impersonation
227
- // https://developers.docusign.com/esign-rest-api/guides/authentication/oauth2-jsonwebtoken#step-1-request-the-authorization-code
228
- func (c * JWTConfig ) UserConsentURL (redirectURL string ) string {
229
- q := make (url.Values )
230
- q .Set ("response_type" , "code" )
231
- q .Set ("scope" , "signature impersonation" )
232
- q .Set ("client_id" , c .IntegratorKey )
233
- q .Set ("redirect_uri" , redirectURL )
240
+ // https://developers.docusign.com/esign-rest-api/guides/authentication/obtaining-consent#individual-consent
241
+ func (c * JWTConfig ) UserConsentURL (redirectURL string , scopes ... string ) string {
242
+ scopeValue := "signature impersonation"
243
+ if len (scopes ) > 0 {
244
+ scopeValue = strings .Join (addUnique (scopes , "impersonation" ), " " )
245
+ }
234
246
// docusign insists upon %20 not + in scope definition
235
- return demoFlag (c .IsDemo ).endpoint ().AuthURL + "?" + replacePlus (q .Encode ())
247
+ return demoFlag (c .IsDemo ).endpoint ().AuthURL + "?" + replacePlus (url.Values {
248
+ "response_type" : {"code" },
249
+ "scope" : {scopeValue },
250
+ "client_id" : {c .IntegratorKey },
251
+ "redirect_uri" : {redirectURL },
252
+ }.Encode ())
236
253
}
237
254
238
- func (c * JWTConfig ) jwtRefresher (apiUserName string , signer jws.Signer ) func (ctx context.Context , tk * oauth2.Token ) (* oauth2.Token , error ) {
255
+ func (c * JWTConfig ) jwtRefresher (apiUserName string , signer jws.Signer , scopes ... string ) func (ctx context.Context , tk * oauth2.Token ) (* oauth2.Token , error ) {
256
+ if len (scopes ) == 0 {
257
+ scopes = []string {"signature" , "impersonation" }
258
+ } else {
259
+ scopes = addUnique (scopes , "impersonation" )
260
+ }
239
261
cfg := & jwt.Config {
240
262
Issuer : c .IntegratorKey ,
241
263
Signer : signer ,
242
264
Subject : apiUserName ,
243
265
Options : c .Options ,
244
- Scopes : [] string { "signature" , "impersonation" } ,
266
+ Scopes : scopes ,
245
267
Audience : demoFlag (c .IsDemo ).tokenURI (),
246
268
TokenURL : demoFlag (c .IsDemo ).endpoint ().TokenURL ,
247
269
HTTPClientFunc : c .HTTPClientFunc ,
@@ -252,16 +274,16 @@ func (c *JWTConfig) jwtRefresher(apiUserName string, signer jws.Signer) func(ctx
252
274
}
253
275
254
276
// Credential returns an *OAuth2Credential. The passed token will be refreshed
255
- // as needed.
256
- func (c * JWTConfig ) Credential (apiUserName string , token * oauth2.Token , u * UserInfo ) (* OAuth2Credential , error ) {
277
+ // as needed. If no scopes listed, signature is assumed.
278
+ func (c * JWTConfig ) Credential (apiUserName string , token * oauth2.Token , u * UserInfo , scopes ... string ) (* OAuth2Credential , error ) {
257
279
signer , err := jws .RS256FromPEM ([]byte (c .PrivateKey ), c .KeyPairID )
258
280
if err != nil {
259
281
return nil , err
260
282
}
261
283
return & OAuth2Credential {
262
284
accountID : c .AccountID ,
263
285
cachedToken : token ,
264
- refresher : c .jwtRefresher (apiUserName , signer ),
286
+ refresher : c .jwtRefresher (apiUserName , signer , scopes ... ),
265
287
cacheFunc : c .CacheFunc ,
266
288
isDemo : demoFlag (c .IsDemo ),
267
289
userInfo : u ,
0 commit comments