From 6f28996586216f4539154b9c03e87c4b6bfe4338 Mon Sep 17 00:00:00 2001 From: Paul Rosania Date: Wed, 4 Feb 2015 00:22:21 -0800 Subject: [PATCH] oauth2: Resource Owner Password Credentials grant Adds support for the Resource Owner Password Credentials grant type, which allows trusted clients to exchange user credentials for an access token directly. This is generally a bad idea, but is extremely useful in some situations, where an external redirect is undesirable or impossible. See https://tools.ietf.org/html/rfc6749#section-4.3 Change-Id: I28efd77957bcf8e1174e93ba0c64a990b94eb839 Reviewed-on: https://go-review.googlesource.com/3862 Reviewed-by: Burcu Dogan --- oauth2.go | 20 ++++++++++++++++++++ oauth2_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/oauth2.go b/oauth2.go index 59e8fd0..6a92d70 100644 --- a/oauth2.go +++ b/oauth2.go @@ -134,6 +134,26 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { return buf.String() } +// PasswordCredentialsToken converts a resource owner username and password +// pair into a token. +// +// Per the RFC, this grant type should only be used "when there is a high +// degree of trust between the resource owner and the client (e.g., the client +// is part of the device operating system or a highly privileged application), +// and when other authorization grant types are not available." +// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. +// +// The HTTP client to use is derived from the context. If nil, +// http.DefaultClient is used. See the Context type's documentation. +func (c *Config) PasswordCredentialsToken(ctx Context, username, password string) (*Token, error) { + return retrieveToken(ctx, c, url.Values{ + "grant_type": {"password"}, + "username": {username}, + "password": {password}, + "scope": condVal(strings.Join(c.Scopes, " ")), + }) +} + // Exchange converts an authorization code into a token. // // It is used after a resource provider redirects the user back diff --git a/oauth2_test.go b/oauth2_test.go index 804098a..f7b5ca6 100644 --- a/oauth2_test.go +++ b/oauth2_test.go @@ -210,6 +210,53 @@ func TestExchangeRequest_NonBasicAuth(t *testing.T) { conf.Exchange(ctx, "code") } +func TestPasswordCredentialsTokenRequest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + expected := "/token" + if r.URL.String() != expected { + t.Errorf("URL = %q; want %q", r.URL, expected) + } + headerAuth := r.Header.Get("Authorization") + expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" + if headerAuth != expected { + t.Errorf("Authorization header = %q; want %q", headerAuth, expected) + } + headerContentType := r.Header.Get("Content-Type") + expected = "application/x-www-form-urlencoded" + if headerContentType != expected { + t.Errorf("Content-Type header = %q; want %q", headerContentType, expected) + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Failed reading request body: %s.", err) + } + expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1" + if string(body) != expected { + t.Errorf("res.Body = %q; want %q", string(body), expected) + } + 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(NoContext, "user1", "password1") + if err != nil { + t.Error(err) + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + expected := "90d64460d14870c08c81352a05dedd3465940a7c" + if tok.AccessToken != expected { + t.Errorf("AccessToken = %q; want %q", tok.AccessToken, expected) + } + expected = "bearer" + if tok.TokenType != expected { + t.Errorf("TokenType = %q; want %q", tok.TokenType, expected) + } +} + func TestTokenRefreshRequest(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() == "/somethingelse" {