Skip to content

Commit

Permalink
Merge dev into master
Browse files Browse the repository at this point in the history
  • Loading branch information
google-oss-bot committed Jan 28, 2021
2 parents 77177c7 + 11fd169 commit a957589
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 44 deletions.
56 changes: 40 additions & 16 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"os"
"strings"
"time"

Expand All @@ -28,9 +29,11 @@ import (
)

const (
authErrorCode = "authErrorCode"
firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
oneHourInSeconds = 3600
authErrorCode = "authErrorCode"
emulatorHostEnvVar = "FIREBASE_AUTH_EMULATOR_HOST"
defaultAuthURL = "https://identitytoolkit.googleapis.com"
firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
oneHourInSeconds = 3600

// SDK-generated error codes
idTokenRevoked = "ID_TOKEN_REVOKED"
Expand Down Expand Up @@ -58,18 +61,27 @@ type Client struct {
// Auth service through firebase.App.
func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error) {
var (
signer cryptoSigner
err error
isEmulator bool
signer cryptoSigner
err error
)

creds, _ := transport.Creds(ctx, conf.Opts...)
authEmulatorHost := os.Getenv(emulatorHostEnvVar)
if authEmulatorHost != "" {
isEmulator = true
signer = emulatedSigner{}
}

if signer == nil {
creds, _ := transport.Creds(ctx, conf.Opts...)

// Initialize a signer by following the go/firebase-admin-sign protocol.
if creds != nil && len(creds.JSON) > 0 {
// If the SDK was initialized with a service account, use it to sign bytes.
signer, err = signerFromCreds(creds.JSON)
if err != nil && err != errNotAServiceAcct {
return nil, err
// Initialize a signer by following the go/firebase-admin-sign protocol.
if creds != nil && len(creds.JSON) > 0 {
// If the SDK was initialized with a service account, use it to sign bytes.
signer, err = signerFromCreds(creds.JSON)
if err != nil && err != errNotAServiceAcct {
return nil, err
}
}
}

Expand All @@ -91,12 +103,12 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
}
}

idTokenVerifier, err := newIDTokenVerifier(ctx, conf.ProjectID)
idTokenVerifier, err := newIDTokenVerifier(ctx, conf.ProjectID, isEmulator)
if err != nil {
return nil, err
}

cookieVerifier, err := newSessionCookieVerifier(ctx, conf.ProjectID)
cookieVerifier, err := newSessionCookieVerifier(ctx, conf.ProjectID, isEmulator)
if err != nil {
return nil, err
}
Expand All @@ -112,9 +124,20 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
internal.WithHeader("X-Client-Version", fmt.Sprintf("Go/Admin/%s", conf.Version)),
}

baseURL := defaultAuthURL
if isEmulator {
baseURL = fmt.Sprintf("http://%s/identitytoolkit.googleapis.com", authEmulatorHost)
}
idToolkitV1Endpoint := fmt.Sprintf("%s/v1", baseURL)
idToolkitV2Beta1Endpoint := fmt.Sprintf("%s/v2beta1", baseURL)
userManagementEndpoint := idToolkitV1Endpoint
providerConfigEndpoint := idToolkitV2Beta1Endpoint
tenantMgtEndpoint := idToolkitV2Beta1Endpoint

base := &baseClient{
userManagementEndpoint: idToolkitV1Endpoint,
userManagementEndpoint: userManagementEndpoint,
providerConfigEndpoint: providerConfigEndpoint,
tenantMgtEndpoint: tenantMgtEndpoint,
projectID: conf.ProjectID,
httpClient: hc,
idTokenVerifier: idTokenVerifier,
Expand Down Expand Up @@ -177,7 +200,7 @@ func (c *baseClient) CustomTokenWithClaims(ctx context.Context, uid string, devC

now := c.clock.Now().Unix()
info := &jwtInfo{
header: jwtHeader{Algorithm: "RS256", Type: "JWT"},
header: jwtHeader{Algorithm: c.signer.Algorithm(), Type: "JWT"},
payload: &customToken{
Iss: iss,
Sub: iss,
Expand Down Expand Up @@ -234,6 +257,7 @@ type FirebaseInfo struct {
type baseClient struct {
userManagementEndpoint string
providerConfigEndpoint string
tenantMgtEndpoint string
projectID string
tenantID string
httpClient *internal.HTTPClient
Expand Down
86 changes: 72 additions & 14 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import (
)

const (
credEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
testProjectID = "mock-project-id"
testVersion = "test-version"
credEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
testProjectID = "mock-project-id"
testVersion = "test-version"
defaultIDToolkitV1Endpoint = "https://identitytoolkit.googleapis.com/v1"
defaultIDToolkitV2Beta1Endpoint = "https://identitytoolkit.googleapis.com/v2beta1"
)

var (
Expand Down Expand Up @@ -280,6 +282,38 @@ func TestNewClientExplicitNoAuth(t *testing.T) {
}
}

func TestNewClientEmulatorHostEnvVar(t *testing.T) {
emulatorHost := "localhost:9099"
idToolkitV1Endpoint := "http://localhost:9099/identitytoolkit.googleapis.com/v1"
idToolkitV2Beta1Endpoint := "http://localhost:9099/identitytoolkit.googleapis.com/v2beta1"

os.Setenv(emulatorHostEnvVar, emulatorHost)
defer os.Unsetenv(emulatorHostEnvVar)

conf := &internal.AuthConfig{
Opts: []option.ClientOption{
option.WithoutAuthentication(),
},
}
client, err := NewClient(context.Background(), conf)
if err != nil {
t.Fatal(err)
}
baseClient := client.baseClient
if baseClient.userManagementEndpoint != idToolkitV1Endpoint {
t.Errorf("baseClient.userManagementEndpoint = %q; want = %q", baseClient.userManagementEndpoint, idToolkitV1Endpoint)
}
if baseClient.providerConfigEndpoint != idToolkitV2Beta1Endpoint {
t.Errorf("baseClient.providerConfigEndpoint = %q; want = %q", baseClient.providerConfigEndpoint, idToolkitV2Beta1Endpoint)
}
if baseClient.tenantMgtEndpoint != idToolkitV2Beta1Endpoint {
t.Errorf("baseClient.tenantMgtEndpoint = %q; want = %q", baseClient.tenantMgtEndpoint, idToolkitV2Beta1Endpoint)
}
if _, ok := baseClient.signer.(emulatedSigner); !ok {
t.Errorf("baseClient.signer = %#v; want = %#v", baseClient.signer, emulatedSigner{})
}
}

func TestCustomToken(t *testing.T) {
client := &Client{
baseClient: &baseClient{
Expand Down Expand Up @@ -663,6 +697,27 @@ func TestVerifyIDTokenWithNoProjectID(t *testing.T) {
}
}

func TestVerifyIDTokenInEmulatorMode(t *testing.T) {
os.Setenv(emulatorHostEnvVar, "localhost:9099")
defer os.Unsetenv(emulatorHostEnvVar)

conf := &internal.AuthConfig{
ProjectID: "",
Opts: optsWithTokenSource,
}
c, err := NewClient(context.Background(), conf)
if err != nil {
t.Fatal(err)
}

defer func() {
if r := recover(); r == nil {
t.Error("VeridyIDToken() didn't panic")
}
}()
c.VerifyIDToken(context.Background(), testIDToken)
}

func TestCustomTokenVerification(t *testing.T) {
client := &Client{
baseClient: &baseClient{
Expand All @@ -682,7 +737,7 @@ func TestCustomTokenVerification(t *testing.T) {
}

func TestCertificateRequestError(t *testing.T) {
tv, err := newIDTokenVerifier(context.Background(), testProjectID)
tv, err := newIDTokenVerifier(context.Background(), testProjectID, false)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -1022,7 +1077,7 @@ func signerForTests(ctx context.Context) (cryptoSigner, error) {
}

func idTokenVerifierForTests(ctx context.Context) (*tokenVerifier, error) {
tv, err := newIDTokenVerifier(ctx, testProjectID)
tv, err := newIDTokenVerifier(ctx, testProjectID, false)
if err != nil {
return nil, err
}
Expand All @@ -1036,7 +1091,7 @@ func idTokenVerifierForTests(ctx context.Context) (*tokenVerifier, error) {
}

func cookieVerifierForTests(ctx context.Context) (*tokenVerifier, error) {
tv, err := newSessionCookieVerifier(ctx, testProjectID)
tv, err := newSessionCookieVerifier(ctx, testProjectID, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1163,23 +1218,26 @@ func checkCookieVerifier(tv *tokenVerifier, projectID string) error {
}

func checkBaseClient(client *Client, wantProjectID string) error {
umc := client.baseClient
if umc.userManagementEndpoint != idToolkitV1Endpoint {
return fmt.Errorf("userManagementEndpoint = %q; want = %q", umc.userManagementEndpoint, idToolkitV1Endpoint)
baseClient := client.baseClient
if baseClient.userManagementEndpoint != defaultIDToolkitV1Endpoint {
return fmt.Errorf("userManagementEndpoint = %q; want = %q", baseClient.userManagementEndpoint, defaultIDToolkitV1Endpoint)
}
if baseClient.providerConfigEndpoint != defaultIDToolkitV2Beta1Endpoint {
return fmt.Errorf("providerConfigEndpoint = %q; want = %q", baseClient.providerConfigEndpoint, defaultIDToolkitV2Beta1Endpoint)
}
if umc.providerConfigEndpoint != providerConfigEndpoint {
return fmt.Errorf("providerConfigEndpoint = %q; want = %q", umc.providerConfigEndpoint, providerConfigEndpoint)
if baseClient.tenantMgtEndpoint != defaultIDToolkitV2Beta1Endpoint {
return fmt.Errorf("providerConfigEndpoint = %q; want = %q", baseClient.providerConfigEndpoint, defaultIDToolkitV2Beta1Endpoint)
}
if umc.projectID != wantProjectID {
return fmt.Errorf("projectID = %q; want = %q", umc.projectID, wantProjectID)
if baseClient.projectID != wantProjectID {
return fmt.Errorf("projectID = %q; want = %q", baseClient.projectID, wantProjectID)
}

req, err := http.NewRequest(http.MethodGet, "https://firebase.google.com", nil)
if err != nil {
return err
}

for _, opt := range umc.httpClient.Opts {
for _, opt := range baseClient.httpClient.Opts {
opt(req)
}
version := req.Header.Get("X-Client-Version")
Expand Down
2 changes: 0 additions & 2 deletions auth/provider_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import (
)

const (
providerConfigEndpoint = "https://identitytoolkit.googleapis.com/v2beta1"

maxConfigs = 100

idpEntityIDKey = "idpConfig.idpEntityId"
Expand Down
6 changes: 1 addition & 5 deletions auth/tenant_mgt.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ import (
"google.golang.org/api/iterator"
)

const (
tenantMgtEndpoint = "https://identitytoolkit.googleapis.com/v2beta1"
)

// Tenant represents a tenant in a multi-tenant application.
//
// Multi-tenancy support requires Google Cloud's Identity Platform (GCIP). To learn more about GCIP,
Expand Down Expand Up @@ -88,7 +84,7 @@ type TenantManager struct {
func newTenantManager(client *internal.HTTPClient, conf *internal.AuthConfig, base *baseClient) *TenantManager {
return &TenantManager{
base: base,
endpoint: tenantMgtEndpoint,
endpoint: base.tenantMgtEndpoint,
projectID: conf.ProjectID,
httpClient: client,
}
Expand Down
30 changes: 30 additions & 0 deletions auth/token_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ import (
"firebase.google.com/go/v4/internal"
)

const (
algorithmNone = "none"
algorithmRS256 = "RS256"

emulatorEmail = "firebase-auth-emulator@example.com"
)

type jwtHeader struct {
Algorithm string `json:"alg"`
Type string `json:"typ"`
Expand Down Expand Up @@ -88,6 +95,7 @@ type serviceAccount struct {

// cryptoSigner is used to cryptographically sign data, and query the identity of the signer.
type cryptoSigner interface {
Algorithm() string
Sign(context.Context, []byte) ([]byte, error)
Email(context.Context) (string, error)
}
Expand Down Expand Up @@ -133,6 +141,10 @@ func newServiceAccountSigner(sa serviceAccount) (*serviceAccountSigner, error) {
}, nil
}

func (s serviceAccountSigner) Algorithm() string {
return algorithmRS256
}

func (s serviceAccountSigner) Sign(ctx context.Context, b []byte) ([]byte, error) {
hash := sha256.New()
hash.Write(b)
Expand Down Expand Up @@ -173,6 +185,10 @@ func newIAMSigner(ctx context.Context, config *internal.AuthConfig) (*iamSigner,
}, nil
}

func (s iamSigner) Algorithm() string {
return algorithmRS256
}

func (s iamSigner) Sign(ctx context.Context, b []byte) ([]byte, error) {
account, err := s.Email(ctx)
if err != nil {
Expand Down Expand Up @@ -245,3 +261,17 @@ func (s iamSigner) callMetadataService(ctx context.Context) (string, error) {

return result, nil
}

type emulatedSigner struct{}

func (s emulatedSigner) Algorithm() string {
return algorithmNone
}

func (s emulatedSigner) Email(context.Context) (string, error) {
return emulatorEmail, nil
}

func (s emulatedSigner) Sign(context.Context, []byte) ([]byte, error) {
return []byte(""), nil
}
Loading

0 comments on commit a957589

Please sign in to comment.