diff --git a/oauth2.go b/oauth2.go index 90a2c3d6d..d3bf5073d 100644 --- a/oauth2.go +++ b/oauth2.go @@ -195,7 +195,7 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { // See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. // // The provided context optionally controls which HTTP client is used. See the HTTPClient variable. -func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { +func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string, opts ...AuthCodeOption) (*Token, error) { v := url.Values{ "grant_type": {"password"}, "username": {username}, @@ -204,6 +204,9 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor if len(c.Scopes) > 0 { v.Set("scope", strings.Join(c.Scopes, " ")) } + for _, opt := range opts { + opt.setValue(v) + } return retrieveToken(ctx, c, v) } diff --git a/oauth2_test.go b/oauth2_test.go index 37f0580d7..f663e0050 100644 --- a/oauth2_test.go +++ b/oauth2_test.go @@ -423,6 +423,105 @@ func TestPasswordCredentialsTokenRequest(t *testing.T) { } } +func TestPasswordCredentialsTokenRequest_AuthCodeOption(t *testing.T) { + testCases := map[string]struct { + username string + password string + authCodeOptions []AuthCodeOption + wantURLString string + wantHeaderAuth string + wantHeaderContentType string + wantBody string + wantAccessToken string + wantTokenType string + }{ + "empty": { + username: "user1", + password: "password1", + authCodeOptions: []AuthCodeOption{}, + wantURLString: "/token", + wantHeaderAuth: "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=", + wantHeaderContentType: "application/x-www-form-urlencoded", + wantBody: "grant_type=password&password=password1&scope=scope1+scope2&username=user1", + wantAccessToken: "90d64460d14870c08c81352a05dedd3465940a7c", + wantTokenType: "bearer", + }, + "foo=bar": { + username: "user1", + password: "password1", + authCodeOptions: []AuthCodeOption{SetAuthURLParam("foo", "bar")}, + wantURLString: "/token", + wantHeaderAuth: "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=", + wantHeaderContentType: "application/x-www-form-urlencoded", + wantBody: "foo=bar&grant_type=password&password=password1&scope=scope1+scope2&username=user1", + wantAccessToken: "90d64460d14870c08c81352a05dedd3465940a7c", + wantTokenType: "bearer", + }, + "zoo=baz": { + username: "user1", + password: "password1", + authCodeOptions: []AuthCodeOption{SetAuthURLParam("zoo", "baz")}, + wantURLString: "/token", + wantHeaderAuth: "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=", + wantHeaderContentType: "application/x-www-form-urlencoded", + wantBody: "grant_type=password&password=password1&scope=scope1+scope2&username=user1&zoo=baz", + wantAccessToken: "90d64460d14870c08c81352a05dedd3465940a7c", + wantTokenType: "bearer", + }, + "foo=bar&zoo=baz": { + username: "user1", + password: "password1", + authCodeOptions: []AuthCodeOption{SetAuthURLParam("foo", "bar"), SetAuthURLParam("zoo", "baz")}, + wantURLString: "/token", + wantHeaderAuth: "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=", + wantHeaderContentType: "application/x-www-form-urlencoded", + wantBody: "foo=bar&grant_type=password&password=password1&scope=scope1+scope2&username=user1&zoo=baz", + wantAccessToken: "90d64460d14870c08c81352a05dedd3465940a7c", + wantTokenType: "bearer", + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + if r.URL.String() != tc.wantURLString { + t.Errorf("URL = %q; want %q", r.URL, tc.wantURLString) + } + if r.Header.Get("Authorization") != tc.wantHeaderAuth { + t.Errorf("Authorization header = %q; want %q", r.Header.Get("Authorization"), tc.wantHeaderAuth) + } + if r.Header.Get("Content-Type") != tc.wantHeaderContentType { + t.Errorf("Content-Type header = %q; want %q", r.Header.Get("Content-Type"), tc.wantHeaderContentType) + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Failed reading request body: %s.", err) + } + if string(body) != tc.wantBody { + t.Errorf("res.Body = %q; want %q", string(body), tc.wantBody) + } + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer")) + })) + defer ts.Close() + conf := newConf(ts.URL) + tok, err := conf.PasswordCredentialsToken(context.Background(), "user1", "password1", tc.authCodeOptions...) + if err != nil { + t.Error(err) + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + if tok.AccessToken != tc.wantAccessToken { + t.Errorf("AccessToken = %q; want %q", tok.AccessToken, tc.wantAccessToken) + } + if tok.TokenType != tc.wantTokenType { + t.Errorf("TokenType = %q; want %q", tok.TokenType, tc.wantTokenType) + } + }) + } +} + func TestTokenRefreshRequest(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() == "/somethingelse" {