From c4d44ca3c921aef57756105fd5fe0a2194a1f495 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 12:44:20 -0700 Subject: [PATCH 1/8] Add examples for regular and JWT configs and transport init. --- example_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ oauth2.go | 26 ------------- 2 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 example_test.go diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..bcd8713 --- /dev/null +++ b/example_test.go @@ -0,0 +1,97 @@ +package oauth2 + +import ( + "fmt" + "log" + "net/http" +) + +func Example_config() { + conf, err := NewConfig(&Options{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + RedirectURL: "YOUR_REDIRECT_URL", + Scopes: []string{"SCOPE1", "SCOPE2"}, + }, + "https://provider.com/o/oauth2/auth", + "https://provider.com/o/oauth2/token") + if err != nil { + log.Fatal(err) + } + + // Redirect user to consent page to ask for permission + // for the scopes specified above. + url, _ := conf.AuthCodeURL("") + fmt.Printf("Visit the URL for the auth dialog: %v", url) + + // Use the exchange code that is handled by the redirect URL. + // NewTransportWithCode will do the handshake to retrieve + // an access token and iniate a Transport that is + // authorized and authenticated the retrieved token. + var exchangeCode string + fmt.Scanln(&exchangeCode) + t, err := conf.NewTransportWithCode(exchangeCode) + if err != nil { + log.Fatal(err) + } + + // You can use t to initiate a new http.Client and + // start making authenticated requests. + client := http.Client{Transport: t} + client.Get("https://host/path") + + // Alternatively, you can initiate a new transport + // with tokens from a cache. + cache := NewFileCache("/path/to/file") + // NewTransportWithCache will try to read the cached + // token, if any error occurs, it returns the error. + // If a token is available at the cache, initiates + // a new transport authorized and authenticated with + // the read token. If token expires, and a new access + // token is retrieved, it writes the newly fetched + // token to the cache. + t, err = conf.NewTransportWithCache(cache) + if err != nil { + log.Fatal(err) + } + client = http.Client{Transport: t} + client.Get("https://host/path") +} + +func Example_jWTConfig() { + conf, _ := NewJWTConfig(&JWTOptions{ + Email: "xxx@developer.gserviceaccount.com", + // The path to the pem file. If you have a p12 file instead, you + // can use `openssl` to export the private key into a pem file. + // $ openssl pkcs12 -in key.p12 -out key.pem -nodes + PemFilename: "/path/to/pem/file.pem", + Scopes: []string{"SCOPE1", "SCOPE2"}, + }, + "https://provider.com/o/oauth2/token") + + // Initiate an http.Client, the following GET request will be + // authorized and authenticated on the behalf of + // xxx@developer.gserviceaccount.com. + client := http.Client{Transport: conf.NewTransport()} + client.Get("https://host/path") + + // If you would like to impersonate a user, you can + // create a transport with a subject. The following GET + // request will be made on the behalf of user@example.com. + client = http.Client{Transport: conf.NewTransportWithUser("user@example.com")} + client.Get("https://host/path") + + // Alternatively you can iniate a transport with + // a token read from the cache. + // If the existing access token expires, and a new access token is + // retrieved, the newly fetched token will be written to the cache. + cache := NewFileCache("/path/to/file") + t, err := conf.NewTransportWithCache(cache) + if err != nil { + log.Fatal(err) + } + client = http.Client{Transport: t} + // The following request will be authorized by the token + // retrieved from the cache. + client.Get("https://host/path") +} diff --git a/oauth2.go b/oauth2.go index b87de7d..27882ac 100644 --- a/oauth2.go +++ b/oauth2.go @@ -5,32 +5,6 @@ // Package oauth2 provides support for making // OAuth2 authorized and authenticated HTTP requests. // It can additionally grant authorization with Bearer JWT. -// -// Example usage: -// -// // Specify your configuration. (typically as a global variable) -// config := oauth2.NewConfig(&oauth2.Options{ -// ClientID: YOUR_CLIENT_ID, -// ClientSecret: YOUR_CLIENT_SECRET, -// RedirectURL: "http://you.example.org/handler", -// Scopes: []string{ "scope1", "scope2" }, -// }, OAUTH2_PROVIDER_AUTH_URL, OAUTH2_PROVIDER_TOKEN_URL) -// -// // A landing page redirects to the OAuth provider to get the auth code. -// func landing(w http.ResponseWriter, r *http.Request) { -// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound) -// } -// -// // The user will be redirected back to this handler, that takes the -// // "code" query parameter and Exchanges it for an access token. -// func handler(w http.ResponseWriter, r *http.Request) { -// t, err := config.NewTransportWithCode(r.FormValue("code")) -// // The Transport now has a valid Token. Create an *http.Client -// // with which we can make authenticated API requests. -// c := t.Client() -// c.Post(...) -// } -// package oauth2 import ( From fe0eecc41c8c4b4607ff751a707a2e584b07115f Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 13:10:10 -0700 Subject: [PATCH 2/8] Some cleanup, adding Google web flow example. --- example_test.go | 22 +++++++++----- google/example_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++ google/google.go | 53 -------------------------------- 3 files changed, 83 insertions(+), 61 deletions(-) create mode 100644 google/example_test.go diff --git a/example_test.go b/example_test.go index bcd8713..02bb183 100644 --- a/example_test.go +++ b/example_test.go @@ -21,7 +21,10 @@ func Example_config() { // Redirect user to consent page to ask for permission // for the scopes specified above. - url, _ := conf.AuthCodeURL("") + url, err := conf.AuthCodeURL("") + if err != nil { + log.Fatal(err) + } fmt.Printf("Visit the URL for the auth dialog: %v", url) // Use the exchange code that is handled by the redirect URL. @@ -29,7 +32,7 @@ func Example_config() { // an access token and iniate a Transport that is // authorized and authenticated the retrieved token. var exchangeCode string - fmt.Scanln(&exchangeCode) + fmt.Scan(&exchangeCode) t, err := conf.NewTransportWithCode(exchangeCode) if err != nil { log.Fatal(err) @@ -38,7 +41,7 @@ func Example_config() { // You can use t to initiate a new http.Client and // start making authenticated requests. client := http.Client{Transport: t} - client.Get("https://host/path") + client.Get("...") // Alternatively, you can initiate a new transport // with tokens from a cache. @@ -55,11 +58,11 @@ func Example_config() { log.Fatal(err) } client = http.Client{Transport: t} - client.Get("https://host/path") + client.Get("...") } func Example_jWTConfig() { - conf, _ := NewJWTConfig(&JWTOptions{ + conf, err := NewJWTConfig(&JWTOptions{ Email: "xxx@developer.gserviceaccount.com", // The path to the pem file. If you have a p12 file instead, you // can use `openssl` to export the private key into a pem file. @@ -68,18 +71,21 @@ func Example_jWTConfig() { Scopes: []string{"SCOPE1", "SCOPE2"}, }, "https://provider.com/o/oauth2/token") + if err != nil { + log.Fatal(err) + } // Initiate an http.Client, the following GET request will be // authorized and authenticated on the behalf of // xxx@developer.gserviceaccount.com. client := http.Client{Transport: conf.NewTransport()} - client.Get("https://host/path") + client.Get("...") // If you would like to impersonate a user, you can // create a transport with a subject. The following GET // request will be made on the behalf of user@example.com. client = http.Client{Transport: conf.NewTransportWithUser("user@example.com")} - client.Get("https://host/path") + client.Get("...") // Alternatively you can iniate a transport with // a token read from the cache. @@ -93,5 +99,5 @@ func Example_jWTConfig() { client = http.Client{Transport: t} // The following request will be authorized by the token // retrieved from the cache. - client.Get("https://host/path") + client.Get("...") } diff --git a/google/example_test.go b/google/example_test.go new file mode 100644 index 0000000..dc98129 --- /dev/null +++ b/google/example_test.go @@ -0,0 +1,69 @@ +package google + +import ( + "log" + "net/http" + + "github.com/golang/oauth2" +) + +func Example_webServer() { + // Your credentials should be obtained from the Google + // Developer Console (https://console.developers.google.com). + config, err := NewConfig(&oauth2.Opts{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + RedirectURL: "YOUR_REDIRECT_URL", + Scopes: []string{ + "https://www.googleapis.com/auth/bigquery", + "https://www.googleapis.com/auth/blogger"}, + }) + if err != nil { + log.Fatal(err) + } + + // Redirect user to Google's consent page to ask for permission + // for the scopes specified above. + url, err := config.AuthCodeURL("") + if err != nil { + log.Fatal(err) + } + fmt.Printf("Visit the URL for the auth dialog: %v", url) + + // Handle the exchange code to initiate a transport + t, err := config.NewTransportWithCode("exchange-code") + if err != nil { + log.Fatal(err) + } + client := http.Client{Transport: t} + client.Get("...") + + // Alternatively you can initiate a new transport + // with a token from a cache. + cache := oauth2.NewFileCache("/path/to/file") + // NewTransportWithCache will try to read the cached + // token, if any error occurs, it returns the error. + // If a token is available at the cache, initiates + // a new transport authorized and authenticated with + // the read token. If token expires, and a new access + // token is retrieved, it writes the newly fetched + // token to the cache. + t, err = config.NewTransportWithCache(cache) + if err != nil { + log.Fatal(err) + } + client = http.Client{Transport: t} + client.Get("...") +} + +func Example_serviceAccounts() { + +} + +func Example_appEngine() { + +} + +func Example_computeEngine() { + +} diff --git a/google/google.go b/google/google.go index 1b56433..fec36de 100644 --- a/google/google.go +++ b/google/google.go @@ -11,59 +11,6 @@ // // For more information, please read // https://developers.google.com/accounts/docs/OAuth2. -// -// Example usage: -// // Web server flow usage: -// // Specify your configuration. -// // Your credentials should be obtained from the Google -// // Developer Console (https://console.developers.google.com). -// var config = google.NewConfig(&oauth2.Opts{ -// ClientID: YOUR_CLIENT_ID, -// ClientSecret: YOUR_CLIENT_SECRET, -// RedirectURL: "http://you.example.org/handler", -// Scopes: []string{ "scope1", "scope2" }, -// }) -// -// // A landing page redirects to Google to get the auth code. -// func landing(w http.ResponseWriter, r *http.Request) { -// http.Redirect(w, r, config.AuthCodeURL(""), http.StatusFound) -// } -// -// // The user will be redirected back to this handler, that takes the -// // "code" query parameter and Exchanges it for an access token. -// func handler(w http.ResponseWriter, r *http.Request) { -// t, err := config.NewTransportWithCode(r.FormValue("code")) -// // The Transport now has a valid Token. Create an *http.Client -// // with which we can make authenticated API requests. -// c := t.Client() -// c.Post(...) -// } -// -// // Service accounts usage: -// // Google Developer Console will provide a p12 file contains -// // a private key. You need to export it to the pem format. -// // Run the following command to generate a pem file that -// // contains your private key: -// // $ openssl pkcs12 -in /path/to/p12key.p12 -out key.pem -nodes -// // Then, specify your configuration. -// var config = google.NewServiceAccountConfig(&oauth2.JWTOpts{ -// Email: "xxx@developer.gserviceaccount.com", -// PemFilename: "/path/to/key.pem", -// Scopes: []string{ -// "https://www.googleapis.com/auth/drive.readonly" -// }, -// }) -// -// // Create a transport. -// t, err := config.NewTransport() -// // Or, you can create a transport that impersonates -// // a Google user. -// t, err := config.NewTransportWithUser(googleUserEmail) -// -// // Create a client to make authorized requests. -// c := t.Client() -// c.Post(...) -// package google import ( From f156f2868ef4246f1600fadccf99a6c58fec81d0 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 13:26:45 -0700 Subject: [PATCH 3/8] Handle scan error. --- example_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example_test.go b/example_test.go index 02bb183..a802cf2 100644 --- a/example_test.go +++ b/example_test.go @@ -32,7 +32,9 @@ func Example_config() { // an access token and iniate a Transport that is // authorized and authenticated the retrieved token. var exchangeCode string - fmt.Scan(&exchangeCode) + if _, err = fmt.Scan(&exchangeCode); err != nil { + log.Fatal(err) + } t, err := conf.NewTransportWithCode(exchangeCode) if err != nil { log.Fatal(err) From 49766fd328180a8a5d5da4402f862c68b36212ad Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 13:27:04 -0700 Subject: [PATCH 4/8] Add app engine and compute engine examples. --- google/example_test.go | 62 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/google/example_test.go b/google/example_test.go index dc98129..a17d461 100644 --- a/google/example_test.go +++ b/google/example_test.go @@ -5,12 +5,13 @@ import ( "net/http" "github.com/golang/oauth2" + "google.golang.org/appengine" ) func Example_webServer() { // Your credentials should be obtained from the Google // Developer Console (https://console.developers.google.com). - config, err := NewConfig(&oauth2.Opts{ + config, err := NewConfig(&oauth2.Options{ ClientID: "YOUR_CLIENT_ID", ClientSecret: "YOUR_CLIENT_SECRET", RedirectURL: "YOUR_REDIRECT_URL", @@ -57,13 +58,70 @@ func Example_webServer() { } func Example_serviceAccounts() { + // Your credentials should be obtained from the Google + // Developer Console (https://console.developers.google.com). + config, err := NewServiceAccountConfig(&oauth2.JWTOptions{ + Email: "xxx@developer.gserviceaccount.com", + // The path to the pem file. If you have a p12 file instead, you + // can use `openssl` to export the private key into a pem file. + // $ openssl pkcs12 -in key.p12 -out key.pem -nodes + PemFilename: "/path/to/pem/file.pem", + Scopes: []string{ + "https://www.googleapis.com/auth/bigquery", + }, + }) + if err != nil { + log.Fatal(err) + } + // Initiate an http.Client, the following GET request will be + // authorized and authenticated on the behalf of + // xxx@developer.gserviceaccount.com. + client := http.Client{Transport: conf.NewTransport()} + client.Get("...") + + // If you would like to impersonate a user, you can + // create a transport with a subject. The following GET + // request will be made on the behalf of user@example.com. + client = http.Client{Transport: conf.NewTransportWithUser("user@example.com")} + client.Get("...") + + // Alternatively you can iniate a transport with + // a token read from the cache. + // If the existing access token expires, and a new access token is + // retrieved, the newly fetched token will be written to the cache. + cache := NewFileCache("/path/to/file") + t, err := conf.NewTransportWithCache(cache) + if err != nil { + log.Fatal(err) + } + client = http.Client{Transport: t} + // The following request will be authorized by the token + // retrieved from the cache. + client.Get("...") } func Example_appEngine() { + context := appengine.NewContext(nil) + config, err := NewAppEngineConfig(context, []string{ + "https://www.googleapis.com/auth/bigquery", + }) + if err != nil { + log.Fatal(err) + } + // The following client will be authorized by the App Engine + // app's service account for the provided scopes. + client := http.Client{Transport: config.NewTransport()} + client.Get("...") } func Example_computeEngine() { - + // If no other account is specified, "default" is in use. + config, err := NewComputeEngineConfig("") + if err != nil { + log.Fatal(err) + } + client := http.Client{Transport: config.NewTransport()} + client.Get("...") } From cb989650abf58fef376d1cef570d865a6ae4d5ec Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 14:28:46 -0700 Subject: [PATCH 5/8] Fix examples. --- example_test.go | 15 ++++++++++----- google/example_test.go | 32 +++++++++++++++----------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/example_test.go b/example_test.go index a802cf2..b111721 100644 --- a/example_test.go +++ b/example_test.go @@ -1,13 +1,18 @@ -package oauth2 +package oauth2_test import ( "fmt" "log" "net/http" + "testing" + + "github.com/golang/oauth2" ) +func TestA(t *testing.T) {} + func Example_config() { - conf, err := NewConfig(&Options{ + conf, err := oauth2.NewConfig(&oauth2.Options{ ClientID: "YOUR_CLIENT_ID", ClientSecret: "YOUR_CLIENT_SECRET", RedirectURL: "YOUR_REDIRECT_URL", @@ -47,7 +52,7 @@ func Example_config() { // Alternatively, you can initiate a new transport // with tokens from a cache. - cache := NewFileCache("/path/to/file") + cache := oauth2.NewFileCache("/path/to/file") // NewTransportWithCache will try to read the cached // token, if any error occurs, it returns the error. // If a token is available at the cache, initiates @@ -64,7 +69,7 @@ func Example_config() { } func Example_jWTConfig() { - conf, err := NewJWTConfig(&JWTOptions{ + conf, err := oauth2.NewJWTConfig(&oauth2.JWTOptions{ Email: "xxx@developer.gserviceaccount.com", // The path to the pem file. If you have a p12 file instead, you // can use `openssl` to export the private key into a pem file. @@ -93,7 +98,7 @@ func Example_jWTConfig() { // a token read from the cache. // If the existing access token expires, and a new access token is // retrieved, the newly fetched token will be written to the cache. - cache := NewFileCache("/path/to/file") + cache := oauth2.NewFileCache("/path/to/file") t, err := conf.NewTransportWithCache(cache) if err != nil { log.Fatal(err) diff --git a/google/example_test.go b/google/example_test.go index a17d461..c1256b6 100644 --- a/google/example_test.go +++ b/google/example_test.go @@ -1,17 +1,22 @@ -package google +package google_test import ( + "fmt" "log" "net/http" + "testing" "github.com/golang/oauth2" + "github.com/golang/oauth2/google" "google.golang.org/appengine" ) +func TestA(t *testing.T) {} + func Example_webServer() { // Your credentials should be obtained from the Google // Developer Console (https://console.developers.google.com). - config, err := NewConfig(&oauth2.Options{ + config, err := google.NewConfig(&oauth2.Options{ ClientID: "YOUR_CLIENT_ID", ClientSecret: "YOUR_CLIENT_SECRET", RedirectURL: "YOUR_REDIRECT_URL", @@ -60,7 +65,7 @@ func Example_webServer() { func Example_serviceAccounts() { // Your credentials should be obtained from the Google // Developer Console (https://console.developers.google.com). - config, err := NewServiceAccountConfig(&oauth2.JWTOptions{ + config, err := google.NewServiceAccountConfig(&oauth2.JWTOptions{ Email: "xxx@developer.gserviceaccount.com", // The path to the pem file. If you have a p12 file instead, you // can use `openssl` to export the private key into a pem file. @@ -77,21 +82,21 @@ func Example_serviceAccounts() { // Initiate an http.Client, the following GET request will be // authorized and authenticated on the behalf of // xxx@developer.gserviceaccount.com. - client := http.Client{Transport: conf.NewTransport()} + client := http.Client{Transport: config.NewTransport()} client.Get("...") // If you would like to impersonate a user, you can // create a transport with a subject. The following GET // request will be made on the behalf of user@example.com. - client = http.Client{Transport: conf.NewTransportWithUser("user@example.com")} + client = http.Client{Transport: config.NewTransportWithUser("user@example.com")} client.Get("...") // Alternatively you can iniate a transport with // a token read from the cache. // If the existing access token expires, and a new access token is // retrieved, the newly fetched token will be written to the cache. - cache := NewFileCache("/path/to/file") - t, err := conf.NewTransportWithCache(cache) + cache := oauth2.NewFileCache("/path/to/file") + t, err := config.NewTransportWithCache(cache) if err != nil { log.Fatal(err) } @@ -103,13 +108,9 @@ func Example_serviceAccounts() { func Example_appEngine() { context := appengine.NewContext(nil) - config, err := NewAppEngineConfig(context, []string{ + config := google.NewAppEngineConfig(context, []string{ "https://www.googleapis.com/auth/bigquery", }) - if err != nil { - log.Fatal(err) - } - // The following client will be authorized by the App Engine // app's service account for the provided scopes. client := http.Client{Transport: config.NewTransport()} @@ -117,11 +118,8 @@ func Example_appEngine() { } func Example_computeEngine() { - // If no other account is specified, "default" is in use. - config, err := NewComputeEngineConfig("") - if err != nil { - log.Fatal(err) - } + // If no other account is specified, "default" is used. + config := google.NewComputeEngineConfig("") client := http.Client{Transport: config.NewTransport()} client.Get("...") } From 0221bdd0c995f03e583ad5df6614c3c91d31e4cd Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 14:53:39 -0700 Subject: [PATCH 6/8] Adding TODO to remove dummy tests after go1.4. --- example_test.go | 2 ++ google/example_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/example_test.go b/example_test.go index b111721..2b234ec 100644 --- a/example_test.go +++ b/example_test.go @@ -9,6 +9,8 @@ import ( "github.com/golang/oauth2" ) +// TODO(jbd): Remove after Go 1.4. +// Related to https://codereview.appspot.com/107320046 func TestA(t *testing.T) {} func Example_config() { diff --git a/google/example_test.go b/google/example_test.go index c1256b6..be48c4d 100644 --- a/google/example_test.go +++ b/google/example_test.go @@ -11,6 +11,8 @@ import ( "google.golang.org/appengine" ) +// Remove after Go 1.4. +// Related to https://codereview.appspot.com/107320046 func TestA(t *testing.T) {} func Example_webServer() { From dd339c27411525f4b72f163a30db0c3eebadd224 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 15:55:10 -0700 Subject: [PATCH 7/8] Add README. Fixes #2. --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index e69de29..1d8f442 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,11 @@ +# OAuth2 for Go + +oauth2 package contains a client implementation for OAuth 2.0 spec. + +## Installation + +~~~~ +go get github.com/golang/oauth2 +~~~~ + +See [godoc](http://godoc.org/github.com/golang/oauth2) for further documentation and examples. From 45a11119129157a83bf2ef5f4c5a1277165abe64 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 24 Jun 2014 16:00:24 -0700 Subject: [PATCH 8/8] Add instructions for contributing. --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 1d8f442..4993caf 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,20 @@ go get github.com/golang/oauth2 ~~~~ See [godoc](http://godoc.org/github.com/golang/oauth2) for further documentation and examples. + +## Contributing + +Fork the repo, make changes, run the tests and open a pull request. + +Before we can accept any pull requests +we have to jump through a couple of legal hurdles, +primarily a Contributor License Agreement (CLA): + +- **If you are an individual writing original source code** + and you're sure you own the intellectual property, + then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). +- **If you work for a company that wants to allow you to contribute your work**, + then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). + +You can sign these electronically (just scroll to the bottom). +After that, we'll be able to accept your pull requests.