Skip to content

Commit

Permalink
Release 2.5.0 (#91)
Browse files Browse the repository at this point in the history
* Renamed some tests and test parameters for clarity, and adhere to Go conventions (#74)

* clean unused types (#76)

* Create CHANGELOG.md (#75) (#79)

* Create CHANGELOG.md

Initial changelog based on https://firebase.google.com/support/release-notes/admin/go

* change instance ID format (#82)

Changing the format of the "non-existing" instance ID in the integration tests to comply with the expected iid format.

* Import context from golang.org/x/net/ for 1.6 compatibility (#87)

* import golang.org/x/net/context instead of context for 1.6 compatibility

* Document non existing name in integration tests for iid (#85)

* Revoke Tokens (#77)

Adding TokensValidAfterMillis property, RevokeRefreshTokens(), and VerifyIDTokenAndCheckRevoked().

* Firebase Cloud Messaging API (#81)

* Adding Firebase Cloud Messaging (#62)

* initial commit for adding Firebase Cloud Messaging

* add validator

* use http const in messaging test

* add client version header for stats

* init integration test

* add integration test (validated on IOS today)

* add comment with URL to enable Firebase Cloud Messaging API

* fix broken test

* add integration tests

* accept a Message instead of RequestMessage + and rename method + send  / sendDryRun

* update fcm url

* rollback url endpoint

* fix http constants, change responseMessage visibility, change map[string]interface{} as map[string]string

* fix http constants

* fix integration tests

* fix APNS naming

* add validators

* Added APNS types; Updated tests

* Added more tests; Fixed APNS serialization

* Updated documentation

* Improved error handling inFCM

* Added utils file

* Updated integration tests

* Implemented topic management operations

* Added integration tests

* Updated CHANGELOG

* Addressing code review comments

* Supporting 0 valued Aps.Badge

* Addressing some review comments

* Removed some unused vars

* Accepting prefixed topic names (#84)

* Accepting prefixed topic named

* Added a comment

* Using new FCM error codes (#89)

* Bumped version to 2.5.0 (#90)
  • Loading branch information
hiranya911 authored Feb 14, 2018
1 parent 6274018 commit f1f98c4
Show file tree
Hide file tree
Showing 23 changed files with 2,000 additions and 138 deletions.
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Unreleased
-

-

# v2.5.0

- [changed] Import context from `golang.org/x/net` for 1.6 compatibility

### Cloud Messaging

- [added] Added the `messaging` package for sending Firebase notifications
and managing topic subscriptions.

### Authentication

- [added] A new [`VerifyIDTokenAndCheckRevoked()`](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken)
function has been added to check for revoked ID tokens.
- [added] A new [`RevokeRefreshTokens()`](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens)
function has been added to invalidate all refresh tokens issued to a user.
- [added] A new property `TokensValidAfterMillis` has been added to the
['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord)
type, which stores the time of the revocation truncated to 1 second accuracy.

# v2.4.0

Expand Down
34 changes: 34 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,26 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac
return encodeToken(c.snr, defaultHeader(), payload)
}

// RevokeRefreshTokens revokes all refresh tokens issued to a user.
//
// RevokeRefreshTokens updates the user's TokensValidAfterMillis to the current UTC second.
// It is important that the server on which this is called has its clock set correctly and synchronized.
//
// While this revokes all sessions for a specified user and disables any new ID tokens for existing sessions
// from getting minted, existing ID tokens may remain active until their natural expiration (one hour).
// To verify that ID tokens are revoked, use `verifyIdTokenAndCheckRevoked(ctx, idToken)`.
func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error {
return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshTokens())
}

// VerifyIDToken verifies the signature and payload of the provided ID token.
//
// VerifyIDToken accepts a signed JWT token string, and verifies that it is current, issued for the
// correct Firebase project, and signed by the Google Firebase services in the cloud. It returns
// a Token containing the decoded claims in the input JWT. See
// https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for
// more details on how to obtain an ID token in a client app.
// This does not check whether or not the token has been revoked. See `VerifyIDTokenAndCheckRevoked` below.
func (c *Client) VerifyIDToken(idToken string) (*Token, error) {
if c.projectID == "" {
return nil, errors.New("project id not available")
Expand Down Expand Up @@ -237,6 +250,27 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) {
return p, nil
}

// VerifyIDTokenAndCheckRevoked verifies the provided ID token and checks it has not been revoked.
//
// VerifyIDTokenAndCheckRevoked verifies the signature and payload of the provided ID token and
// checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT.
func (c *Client) VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken string) (*Token, error) {
p, err := c.VerifyIDToken(idToken)
if err != nil {
return nil, err
}

user, err := c.GetUser(ctx, p.UID)
if err != nil {
return nil, err
}

if p.IssuedAt*1000 < user.TokensValidAfterMillis {
return nil, fmt.Errorf("ID token has been revoked")
}
return p, nil
}

func parseKey(key string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(key))
if block == nil {
Expand Down
2 changes: 1 addition & 1 deletion auth/auth_std.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package auth

import "context"
import "golang.org/x/net/context"

func newSigner(ctx context.Context) (signer, error) {
return serviceAcctSigner{}, nil
Expand Down
49 changes: 47 additions & 2 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ import (
)

var client *Client
var ctx context.Context
var testIDToken string
var testGetUserResponse []byte
var testListUsersResponse []byte

var defaultTestOpts = []option.ClientOption{
option.WithCredentialsFile("../testdata/service_account.json"),
}
Expand All @@ -49,7 +49,6 @@ func TestMain(m *testing.M) {
var (
err error
ks keySource
ctx context.Context
creds *google.DefaultCredentials
opts []option.ClientOption
)
Expand Down Expand Up @@ -193,6 +192,52 @@ func TestCustomTokenInvalidCredential(t *testing.T) {
}
}

func TestVerifyIDTokenAndCheckRevokedValid(t *testing.T) {
s := echoServer(testGetUserResponse, t)
defer s.Close()

ft, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, testIDToken)
if err != nil {
t.Error(err)
}
if ft.Claims["admin"] != true {
t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"])
}
if ft.UID != ft.Subject {
t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject)
}
}

func TestVerifyIDTokenAndCheckRevokedDoNotCheck(t *testing.T) {
s := echoServer(testGetUserResponse, t)
defer s.Close()
tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token

ft, err := s.Client.VerifyIDToken(tok)
if err != nil {
t.Fatal(err)
}
if ft.Claims["admin"] != true {
t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"])
}
if ft.UID != ft.Subject {
t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject)
}
}

func TestVerifyIDTokenAndCheckRevokedInvalidated(t *testing.T) {
s := echoServer(testGetUserResponse, t)
defer s.Close()
tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token

p, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, tok)
we := "ID token has been revoked"
if p != nil || err == nil || err.Error() != we {
t.Errorf("VerifyIDTokenAndCheckRevoked(ctx, token) =(%v, %v); want = (%v, %v)",
p, err, nil, we)
}
}

func TestVerifyIDToken(t *testing.T) {
ft, err := client.VerifyIDToken(testIDToken)
if err != nil {
Expand Down
69 changes: 27 additions & 42 deletions auth/user_mgt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"reflect"
"regexp"
"strings"
"time"

"golang.org/x/net/context"
"google.golang.org/api/identitytoolkit/v3"
Expand All @@ -39,6 +40,7 @@ var commonValidators = map[string]func(interface{}) error{
"password": validatePassword,
"photoUrl": validatePhotoURL,
"localId": validateUID,
"validSince": func(interface{}) error { return nil }, // Needed for preparePayload.
}

// Create a new interface
Expand All @@ -65,6 +67,7 @@ type UserInfo struct {
}

// UserMetadata contains additional metadata associated with a user account.
// Timestamps are in milliseconds since epoch.
type UserMetadata struct {
CreationTimestamp int64
LastLogInTimestamp int64
Expand All @@ -73,11 +76,12 @@ type UserMetadata struct {
// UserRecord contains metadata associated with a Firebase user account.
type UserRecord struct {
*UserInfo
CustomClaims map[string]interface{}
Disabled bool
EmailVerified bool
ProviderUserInfo []*UserInfo
UserMetadata *UserMetadata
CustomClaims map[string]interface{}
Disabled bool
EmailVerified bool
ProviderUserInfo []*UserInfo
TokensValidAfterMillis int64 // milliseconds since epoch.
UserMetadata *UserMetadata
}

// ExportedUserRecord is the returned user value used when listing all the users.
Expand Down Expand Up @@ -173,6 +177,13 @@ func (u *UserToUpdate) PhoneNumber(phone string) *UserToUpdate { u.set("phoneNum
// PhotoURL setter.
func (u *UserToUpdate) PhotoURL(url string) *UserToUpdate { u.set("photoUrl", url); return u }

// revokeRefreshTokens revokes all refresh tokens for a user by setting the validSince property
// to the present in epoch seconds.
func (u *UserToUpdate) revokeRefreshTokens() *UserToUpdate {
u.set("validSince", time.Now().Unix())
return u
}

// CreateUser creates a new user with the specified properties.
func (c *Client) CreateUser(ctx context.Context, user *UserToCreate) (*UserRecord, error) {
uid, err := c.createUser(ctx, user)
Expand Down Expand Up @@ -471,7 +482,12 @@ func (u *UserToUpdate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi
if err := validate(v); err != nil {
return err
}
reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)).SetString(params[key].(string))
f := reflect.ValueOf(user).Elem().FieldByName(strings.Title(key))
if f.Kind() == reflect.String {
f.SetString(params[key].(string))
} else if f.Kind() == reflect.Int64 {
f.SetInt(params[key].(int64))
}
}
}
if params["disableUser"] != nil {
Expand All @@ -498,37 +514,6 @@ func (u *UserToUpdate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi

// End of validators

// Response Types -------------------------------

type getUserResponse struct {
RequestType string
Users []responseUserRecord
}

type responseUserRecord struct {
UID string
DisplayName string
Email string
PhoneNumber string
PhotoURL string
CreationTimestamp int64
LastLogInTimestamp int64
ProviderID string
CustomClaims string
Disabled bool
EmailVerified bool
ProviderUserInfo []*UserInfo
PasswordHash string
PasswordSalt string
ValidSince int64
}

type listUsersResponse struct {
RequestType string
Users []responseUserRecord
NextPage string
}

// Helper functions for retrieval and HTTP calls.

func (c *Client) createUser(ctx context.Context, user *UserToCreate) (string, error) {
Expand Down Expand Up @@ -559,7 +544,6 @@ func (c *Client) updateUser(ctx context.Context, uid string, user *UserToUpdate)
if user == nil || user.params == nil {
return fmt.Errorf("update parameters must not be nil or empty")
}

request := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{
LocalId: uid,
}
Expand Down Expand Up @@ -628,10 +612,11 @@ func makeExportedUser(r *identitytoolkit.UserInfo) (*ExportedUserRecord, error)
ProviderID: defaultProviderID,
UID: r.LocalId,
},
CustomClaims: cc,
Disabled: r.Disabled,
EmailVerified: r.EmailVerified,
ProviderUserInfo: providerUserInfo,
CustomClaims: cc,
Disabled: r.Disabled,
EmailVerified: r.EmailVerified,
ProviderUserInfo: providerUserInfo,
TokensValidAfterMillis: r.ValidSince * 1000,
UserMetadata: &UserMetadata{
LastLogInTimestamp: r.LastLoginAt,
CreationTimestamp: r.CreatedAt,
Expand Down
Loading

0 comments on commit f1f98c4

Please sign in to comment.