Skip to content

Commit

Permalink
Port Google Authenticator Go into oauth2l/go
Browse files Browse the repository at this point in the history
  • Loading branch information
shinfan committed Aug 10, 2018
1 parent 215b0ba commit bb7a030
Show file tree
Hide file tree
Showing 22 changed files with 2,467 additions and 0 deletions.
127 changes: 127 additions & 0 deletions go/sgauth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
Google Authenticator
======

This is the initial repository of Google Authenticator --- a _shard-agnostic_ client library that
provides a unified future-proof interface for Google API authentication.

The project is still at very early stage so __everything is subject to change__.

Concept
-------
Google Authenticator is a future-proof client library that supports __Sharded Google__ and aims
to simplify the developer experience with Google API authentication.


Comparing with existing Google Authentication Libraries, it bring the following advantages:

- __Lightweight concept:__ Decouple the authentication client from underlying workflow.
Application only provides credentials and then make the API call.
As a result, developers should only need minimum knowledge about the authentication workflow.

- __Unified interface:__ The developer only needs to provide a general settings object. This unified credential object
is an extensible structure that can contain arbitrary type of credentials.

Quickstart
----------

To use the authenticator library in your application simply import the package in your source
code:

```go
import "github.com/shinfan/sgauth"
```

To use the authenticator to call Google APIs, simply create a authenticator settings object with
the credentials supported by Google APIs, and use the settings to create the client.
For example, to call Google API with HTTP and API key:

```go
import "github.com/shinfan/sgauth"

// Create the settings with pasted API key.
settings := &sgauth.Settings{
APIKey: "YOUR_API_KEY",
}
// Create the HTTP client with the settings using authenticator.
http, err := sgauth.NewHTTPClient(ctx, createSettings(args))
if err != nil {
// Call Google API here
}
```

Credentials
-----------

To authenticate against Google API, users need to provide required credentials.
The authenticator takes a general settings object that supports multiple types of credentials:

- __Service account JSON__: You can explicitly set the JSON string downloaded from Pantheon so
it can be used by either OAuth or JWT auth flow. If you prefer to use the JWT token authentication
flow, the `aud` value has to be provided. Alternatively, you can use the OAuth flow where you
need to specify the `scope` value.

- __API Key__: The Google API key.

- __Application Default Credentials__: If no credentials set explicitly, Google Authenticator
will try to look for your service account JSON file at the default path --- the path specified
by the `$GOOGLE_APPLICATION_CREDENTIAL` environment variable.

Protocols
---------

Google authenticator supports three protocols which are widely supported by Google APIs:
__REST, gRPC, ProtoRPC__

To use the library calling __REST APIs__, simply create a HTTP client:
```go
import "github.com/shinfan/sgauth"

// Create the settings
settings := &sgauth.Settings{
// Config your credential settings
}
// Create the HTTP client with the settings using authenticator.
http, err := sgauth.NewHTTPClient(ctx, createSettings(args))
if err != nil {
// Call REST Google API here
}
```

Or you can use the library with a __gRPC API client__:

```go
import "github.com/shinfan/sgauth"

// Create the settings
settings := &sgauth.Settings{
// Config your credential settings
}
// Create the gRPC connection with the settings using authenticator.
conn, err := sgauth.NewGrpcConn(ctx, createSettings(args), "YOUR_HOST", "YOUR_PORT")
if err != nil {
return nil, err
}
client := library.NewLibraryServiceClient(conn)
```

To use the library calling __ProtoRPC APIs__:
```go
import "github.com/shinfan/sgauth"
import "github.com/wora/protorpc/client"

// Create the settings
settings := &sgauth.Settings{
// Config your credential settings
}
// Create the HTTP client with the settings using authenticator.
http, err := sgauth.NewHTTPClient(ctx, createSettings(args))
if err != nil {
// Call REST Google API here
}
client := &client.Client{
HTTP: http,
BaseURL: "YOUR_PROTORPC_BASE_URL",
UserAgent: "protorpc/0.1",
}
```

99 changes: 99 additions & 0 deletions go/sgauth/appengine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sgauth

import (
"sort"
"strings"
"sync"
"time"

"golang.org/x/net/context"
"github.com/shinfan/sgauth/internal"
)

// appengineFlex is set at init time by appengineflex_hook.go. If true, we are on App Engine Flex.
var appengineFlex bool

// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)

// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
var appengineAppIDFunc func(c context.Context) string

// AppEngineTokenSource returns a token source that fetches tokens
// issued to the current App Engine application's service account.
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
// that involves user accounts, see oauth2.Config instead.
//
// The provided context must have come from appengine.NewContext.
func AppEngineTokenSource(ctx context.Context, scope ...string) internal.TokenSource {
if appengineTokenFunc == nil {
panic("google: AppEngineTokenSource can only be used on App Engine.")
}
scopes := append([]string{}, scope...)
sort.Strings(scopes)
return &appEngineTokenSource{
ctx: ctx,
scopes: scopes,
key: strings.Join(scopes, " "),
}
}

// aeTokens helps the fetched tokens to be reused until their expiration.
var (
aeTokensMu sync.Mutex
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
)

type tokenLock struct {
mu sync.Mutex // guards t; held while fetching or updating t
t *internal.Token
}

type appEngineTokenSource struct {
ctx context.Context
scopes []string
key string // to aeTokens map; space-separated scopes
}

func (ts *appEngineTokenSource) Token() (*internal.Token, error) {
if appengineTokenFunc == nil {
panic("google: AppEngineTokenSource can only be used on App Engine.")
}

aeTokensMu.Lock()
tok, ok := aeTokens[ts.key]
if !ok {
tok = &tokenLock{}
aeTokens[ts.key] = tok
}
aeTokensMu.Unlock()

tok.mu.Lock()
defer tok.mu.Unlock()
if tok.t.Valid() {
return tok.t, nil
}
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
if err != nil {
return nil, err
}
tok.t = &internal.Token{
AccessToken: access,
Expiry: exp,
}
return tok.t, nil
}
24 changes: 24 additions & 0 deletions go/sgauth/appengine_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// +build appengine appenginevm
package sgauth

import "google.golang.org/appengine"

func init() {
appengineTokenFunc = appengine.AccessToken
appengineAppIDFunc = appengine.AppID
}
70 changes: 70 additions & 0 deletions go/sgauth/compute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sgauth

import (
"cloud.google.com/go/compute/metadata"
"errors"
"encoding/json"
"strings"
"fmt"
"time"
"github.com/shinfan/sgauth/internal"
)

// ComputeTokenSource returns a token source that fetches access tokens
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
// this token source if your program is running on a GCE instance.
// If no account is specified, "default" is used.
// Further information about retrieving access tokens from the GCE metadata
// server can be found at https://cloud.google.com/compute/docs/authentication.
func ComputeTokenSource(account string) internal.TokenSource {
return internal.ReuseTokenSource(nil, computeSource{account: account})
}

type computeSource struct {
account string
}

func (cs computeSource) Token() (*internal.Token, error) {
if !metadata.OnGCE() {
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
}
acct := cs.account
if acct == "" {
acct = "default"
}
tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
if err != nil {
return nil, err
}
var res struct {
AccessToken string `json:"access_token"`
ExpiresInSec int `json:"expires_in"`
TokenType string `json:"token_type"`
}
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
if err != nil {
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
}
if res.ExpiresInSec == 0 || res.AccessToken == "" {
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
}
return &internal.Token{
AccessToken: res.AccessToken,
TokenType: res.TokenType,
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
}, nil
}
31 changes: 31 additions & 0 deletions go/sgauth/credentials/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package credentials

import "github.com/shinfan/sgauth/internal"

// Credentials holds Google credentials, including "Application Default Credentials".
// For more details, see:
// https://developers.google.com/accounts/docs/application-default-credentials
type Credentials struct {
ProjectID string // may be empty
TokenSource internal.TokenSource

// JSON contains the raw bytes from a JSON credentials file.
// This field may be nil if authentication is provided by the
// environment and not with a credentials file, e.g. when code is
// running on Google Cloud Platform.
JSON []byte
}
Loading

0 comments on commit bb7a030

Please sign in to comment.