Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@ type Client struct {
// This function can only be invoked from within the SDK. Client applications should access the
// the Auth service through firebase.App.
func NewClient(c *internal.AuthConfig) (*Client, error) {
ks, err := newHTTPKeySource(c.Ctx, googleCertURL, c.Opts...)
if err != nil {
return nil, err
}

client := &Client{
ks: newHTTPKeySource(googleCertURL),
ks: ks,
projectID: c.ProjectID,
}
if c.Creds == nil || len(c.Creds.JSON) == 0 {
Expand All @@ -83,8 +88,7 @@ func NewClient(c *internal.AuthConfig) (*Client, error) {
ClientEmail string `json:"client_email"`
PrivateKey string `json:"private_key"`
}
err := json.Unmarshal(c.Creds.JSON, &svcAcct)
if err != nil {
if err := json.Unmarshal(c.Creds.JSON, &svcAcct); err != nil {
return nil, err
}

Expand Down
10 changes: 6 additions & 4 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package auth

import (
"errors"
"log"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -98,15 +99,16 @@ func TestMain(m *testing.M) {
opt := option.WithCredentialsFile("../testdata/service_account.json")
creds, err = transport.Creds(context.Background(), opt)
if err != nil {
os.Exit(1)
log.Fatalln(err)
}

client, err = NewClient(&internal.AuthConfig{
Ctx: context.Background(),
Creds: creds,
ProjectID: "mock-project-id",
})
if err != nil {
os.Exit(1)
log.Fatalln(err)
}
client.ks = &fileKeySource{FilePath: "../testdata/public_certs.json"}

Expand Down Expand Up @@ -163,7 +165,7 @@ func TestCustomTokenError(t *testing.T) {
}

func TestCustomTokenInvalidCredential(t *testing.T) {
s, err := NewClient(&internal.AuthConfig{})
s, err := NewClient(&internal.AuthConfig{Ctx: context.Background()})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -226,7 +228,7 @@ func TestVerifyIDTokenError(t *testing.T) {
}

func TestNoProjectID(t *testing.T) {
c, err := NewClient(&internal.AuthConfig{Creds: creds})
c, err := NewClient(&internal.AuthConfig{Ctx: context.Background(), Creds: creds})
if err != nil {
t.Fatal(err)
}
Expand Down
30 changes: 22 additions & 8 deletions auth/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ import (
"strings"
"sync"
"time"

"golang.org/x/net/context"

"google.golang.org/api/option"
"google.golang.org/api/transport"
)

type publicKey struct {
Expand Down Expand Up @@ -70,12 +75,24 @@ type httpKeySource struct {
Mutex *sync.Mutex
}

func newHTTPKeySource(uri string) *httpKeySource {
return &httpKeySource{
KeyURI: uri,
Clock: systemClock{},
Mutex: &sync.Mutex{},
func newHTTPKeySource(ctx context.Context, uri string, opts ...option.ClientOption) (*httpKeySource, error) {
var hc *http.Client
if ctx != nil && len(opts) > 0 {
var err error
hc, _, err = transport.NewHTTPClient(ctx, opts...)
if err != nil {
return nil, err
}
} else {
hc = http.DefaultClient
}

return &httpKeySource{
KeyURI: uri,
HTTPClient: hc,
Clock: systemClock{},
Mutex: &sync.Mutex{},
}, nil
}

// Keys returns the RSA Public Keys hosted at this key source's URI. Refreshes the data if
Expand All @@ -99,9 +116,6 @@ func (k *httpKeySource) hasExpired() bool {

func (k *httpKeySource) refreshKeys() error {
k.CachedKeys = nil
if k.HTTPClient == nil {
k.HTTPClient = http.DefaultClient
}
resp, err := k.HTTPClient.Get(k.KeyURI)
if err != nil {
return err
Expand Down
93 changes: 69 additions & 24 deletions auth/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
package auth

import (
"fmt"
"io"
"io/ioutil"
"net/http"
"testing"
"time"

"golang.org/x/net/context"

"google.golang.org/api/option"
)

type mockHTTPResponse struct {
Expand All @@ -37,6 +42,27 @@ type mockReadCloser struct {
closeCount int
}

func newHTTPClient(data []byte) (*http.Client, *mockReadCloser) {
rc := &mockReadCloser{
data: string(data),
closeCount: 0,
}
client := &http.Client{
Transport: &mockHTTPResponse{
Response: http.Response{
Status: "200 OK",
StatusCode: 200,
Header: http.Header{
"Cache-Control": {"public, max-age=100"},
},
Body: rc,
},
Err: nil,
},
}
return client, rc
}

func (r *mockReadCloser) Read(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, nil
Expand All @@ -61,51 +87,70 @@ func TestHTTPKeySource(t *testing.T) {
t.Fatal(err)
}

mc := &mockClock{now: time.Unix(0, 0)}
rc := &mockReadCloser{
data: string(data),
closeCount: 0,
ks, err := newHTTPKeySource(context.Background(), "http://mock.url")
if err != nil {
t.Fatal(err)
}
ks := newHTTPKeySource("http://mock.url")
ks.HTTPClient = &http.Client{
Transport: &mockHTTPResponse{
Response: http.Response{
Status: "200 OK",
StatusCode: 200,
Header: http.Header{
"Cache-Control": {"public, max-age=100"},
},
Body: rc,
},
Err: nil,
},

if ks.HTTPClient == nil {
t.Errorf("HTTPClient = nil; want non-nil")
}
hc, rc := newHTTPClient(data)
ks.HTTPClient = hc
if err := verifyHTTPKeySource(ks, rc); err != nil {
t.Fatal(err)
}
}

func TestHTTPKeySourceWithClient(t *testing.T) {
data, err := ioutil.ReadFile("../testdata/public_certs.json")
if err != nil {
t.Fatal(err)
}

hc, rc := newHTTPClient(data)
ks, err := newHTTPKeySource(context.Background(), "http://mock.url", option.WithHTTPClient(hc))
if err != nil {
t.Fatal(err)
}

if ks.HTTPClient != hc {
t.Errorf("HTTPClient = %v; want %v", ks.HTTPClient, hc)
}
if err := verifyHTTPKeySource(ks, rc); err != nil {
t.Fatal(err)
}
}

func verifyHTTPKeySource(ks *httpKeySource, rc *mockReadCloser) error {
mc := &mockClock{now: time.Unix(0, 0)}
ks.Clock = mc

exp := time.Unix(100, 0)
for i := 0; i <= 100; i++ {
keys, err := ks.Keys()
if err != nil {
t.Fatal(err)
return err
}
if len(keys) != 3 {
t.Errorf("Keys: %d; want: 3", len(keys))
return fmt.Errorf("Keys: %d; want: 3", len(keys))
} else if rc.closeCount != 1 {
t.Errorf("HTTP calls: %d; want: 1", rc.closeCount)
return fmt.Errorf("HTTP calls: %d; want: 1", rc.closeCount)
} else if ks.ExpiryTime != exp {
t.Errorf("Expiry: %v; want: %v", ks.ExpiryTime, exp)
return fmt.Errorf("Expiry: %v; want: %v", ks.ExpiryTime, exp)
}
mc.now = mc.now.Add(time.Second)
}

mc.now = time.Unix(101, 0)
keys, err := ks.Keys()
if err != nil {
t.Fatal(err)
return err
}
if len(keys) != 3 {
t.Errorf("Keys: %d; want: 3", len(keys))
return fmt.Errorf("Keys: %d; want: 3", len(keys))
} else if rc.closeCount != 2 {
t.Errorf("HTTP calls: %d; want: 2", rc.closeCount)
return fmt.Errorf("HTTP calls: %d; want: 2", rc.closeCount)
}
return nil
}
4 changes: 3 additions & 1 deletion firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var firebaseScopes = []string{
}

// Version of the Firebase Go Admin SDK.
const Version = "1.0.0"
const Version = "1.0.1"

// An App holds configuration and state common to all Firebase services that are exposed from the SDK.
type App struct {
Expand All @@ -53,8 +53,10 @@ type Config struct {
// Auth returns an instance of auth.Client.
func (a *App) Auth() (*auth.Client, error) {
conf := &internal.AuthConfig{
Ctx: a.ctx,
Creds: a.creds,
ProjectID: a.projectID,
Opts: a.opts,
}
return auth.NewClient(conf)
}
Expand Down
7 changes: 4 additions & 3 deletions integration/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"testing"
Expand All @@ -37,18 +38,18 @@ var client *auth.Client
func TestMain(m *testing.M) {
flag.Parse()
if testing.Short() {
fmt.Println("skipping auth integration tests in short mode.")
log.Println("skipping auth integration tests in short mode.")
os.Exit(0)
}

app, err := internal.NewTestApp(context.Background())
if err != nil {
os.Exit(1)
log.Fatalln(err)
}

client, err = app.Auth()
if err != nil {
os.Exit(1)
log.Fatalln(err)
}

os.Exit(m.Run())
Expand Down
5 changes: 5 additions & 0 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
package internal

import (
"golang.org/x/net/context"
"google.golang.org/api/option"

"golang.org/x/oauth2/google"
)

// AuthConfig represents the configuration of Firebase Auth service.
type AuthConfig struct {
Ctx context.Context
Opts []option.ClientOption
Creds *google.DefaultCredentials
ProjectID string
}