Skip to content

Commit

Permalink
Update Go examples to use current oauth2 package
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Bishop committed May 21, 2015
1 parent 351782d commit 6dfdbfd
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 36 deletions.
2 changes: 1 addition & 1 deletion go/README
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ To run these code samples, you will need to install the dependent libraries via
the "go get" command. These code samples require the goauth2 and google-api-go-client
libraries which can be installed with the following commands:

go get code.google.com/p/goauth2/oauth
go get golang.org/x/oauth2
go get code.google.com/p/google-api-go-client/youtube/v3

The keyword search and topic search samples can be run via the standard "go run" command
Expand Down
123 changes: 88 additions & 35 deletions go/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
"os/exec"
"path/filepath"
"runtime"
"time"

"code.google.com/p/goauth2/oauth"
"golang.org/x/oauth2"
)

const missingClientSecretsMessage = `
Expand All @@ -33,9 +34,26 @@ https://developers.google.com/api-client-library/python/guide/aaa_client_secrets

var (
clientSecretsFile = flag.String("secrets", "client_secrets.json", "Client Secrets configuration")
cacheFile = flag.String("cache", "request.token", "Token cache file")
cache = flag.String("cache", "request.token", "Token cache file")
)

// CallbackStatus is returned from the oauth2 callback
type CallbackStatus struct {
code string
state string
err error
}

// Cache specifies the methods that implement a Token cache.
type Cache interface {
Token() (*oauth2.Token, error)
PutToken(*oauth2.Token) error
}

// CacheFile implements Cache. Its value is the name of the file in which
// the Token is stored in JSON format.
type CacheFile string

// ClientConfig is a data structure definition for the client_secrets.json file.
// The code unmarshals the JSON configuration file into this structure.
type ClientConfig struct {
Expand Down Expand Up @@ -72,7 +90,7 @@ func openURL(url string) error {

// readConfig reads the configuration from clientSecretsFile.
// It returns an oauth configuration object for use with the Google API client.
func readConfig(scope string) (*oauth.Config, error) {
func readConfig(scope string) (*oauth2.Config, error) {
// Read the secrets file
data, err := ioutil.ReadFile(*clientSecretsFile)
if err != nil {
Expand All @@ -96,39 +114,37 @@ func readConfig(scope string) (*oauth.Config, error) {
return nil, errors.New("Must specify a redirect URI in config file or when creating OAuth client")
}

return &oauth.Config{
ClientId: cfg.Installed.ClientID,
return &oauth2.Config{
ClientID: cfg.Installed.ClientID,
ClientSecret: cfg.Installed.ClientSecret,
Scope: scope,
AuthURL: cfg.Installed.AuthURI,
TokenURL: cfg.Installed.TokenURI,
RedirectURL: redirectUri,
TokenCache: oauth.CacheFile(*cacheFile),
// Get a refresh token so we can use the access token indefinitely
AccessType: "offline",
// If we want a refresh token, we must set this attribute
// to force an approval prompt or the code won't work.
ApprovalPrompt: "force",
Scopes: []string{scope},
Endpoint: oauth2.Endpoint{
AuthURL: cfg.Installed.AuthURI,
TokenURL: cfg.Installed.TokenURI,
},
RedirectURL: redirectUri,
}, nil
}

// startWebServer starts a web server that listens on http://localhost:8080.
// The webserver waits for an oauth code in the three-legged auth flow.
func startWebServer() (codeCh chan string, err error) {
func startWebServer() (callbackCh chan CallbackStatus, err error) {
listener, err := net.Listen("tcp", "localhost:8080")
if err != nil {
return nil, err
}
codeCh = make(chan string)
callbackCh = make(chan CallbackStatus)
go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
codeCh <- code // send code to OAuth flow
cbs := CallbackStatus{}
cbs.state = r.FormValue("state")
cbs.code = r.FormValue("code")
callbackCh <- cbs // send code to OAuth flow
listener.Close()
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code)
fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", cbs.code)
}))

return codeCh, nil
return callbackCh, nil
}

// buildOAuthHTTPClient takes the user through the three-legged OAuth flow.
Expand All @@ -143,45 +159,82 @@ func buildOAuthHTTPClient(scope string) (*http.Client, error) {
return nil, errors.New(msg)
}

transport := &oauth.Transport{Config: config}

// Try to read the token from the cache file.
// If an error occurs, do the three-legged OAuth flow because
// the token is invalid or doesn't exist.
token, err := config.TokenCache.Token()
tokenCache := CacheFile(*cache)
token, err := tokenCache.Token()
if err != nil {

// You must always provide a non-zero string and validate that it matches
// the state query parameter on your redirect callback
randState := fmt.Sprintf("st%d", time.Now().UnixNano())

// Start web server.
// This is how this program receives the authorization code
// when the browser redirects.
codeCh, err := startWebServer()
callbackCh, err := startWebServer()
if err != nil {
return nil, err
}

// Open url in browser
url := config.AuthCodeURL("")
url := config.AuthCodeURL(randState, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
err = openURL(url)
if err != nil {
fmt.Println("Visit the URL below to get a code.",
" This program will pause until the site is visted.")
} else {
fmt.Println("Your browser has been opened to an authorization URL.",
" This program will resume once authorization has been provided.\n")
" This program will resume once authorization has been provided.")
}
fmt.Println(url)

// Wait for the web server to get the code.
code := <-codeCh
cbs := <-callbackCh

if cbs.state != randState {
return nil, fmt.Errorf("expecting state '%s', received state '%s'", randState, cbs.state)
}

// This code caches the authorization code on the local
// filesystem, if necessary, as long as the TokenCache
// attribute in the config is set.
token, err = transport.Exchange(code)
token, err = config.Exchange(oauth2.NoContext, cbs.code)
if err != nil {
return nil, err
}
err = tokenCache.PutToken(token)
if err != nil {
return nil, err
}
}

transport.Token = token
return transport.Client(), nil
return config.Client(oauth2.NoContext, token), nil
}

// Token retreives the token from the token cache
func (f CacheFile) Token() (*oauth2.Token, error) {
file, err := os.Open(string(f))
if err != nil {
return nil, fmt.Errorf("CacheFile.Token: %s", err.Error())
}
defer file.Close()
tok := &oauth2.Token{}
if err := json.NewDecoder(file).Decode(tok); err != nil {
return nil, fmt.Errorf("CacheFile.Token: %s", err.Error())
}
return tok, nil
}

// PutToken stores the token in the token cache
func (f CacheFile) PutToken(tok *oauth2.Token) error {
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("CacheFile.PutToken: %s", err.Error())
}
if err := json.NewEncoder(file).Encode(tok); err != nil {
file.Close()
return fmt.Errorf("CacheFile.PutToken: %s", err.Error())
}
if err := file.Close(); err != nil {
return fmt.Errorf("CacheFile.PutToken: %s", err.Error())
}
return nil
}

0 comments on commit 6dfdbfd

Please sign in to comment.