github.com/schmorrison/Zoho@v1.1.4/storage.go (about)

     1  package zoho
     2  
     3  import (
     4  	"encoding/gob"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"os"
     9  	"time"
    10  
    11  	"google.golang.org/appengine"
    12  	"google.golang.org/appengine/datastore"
    13  )
    14  
    15  // TokenLoaderSaver is an interface that can be implemented when using a system that does
    16  // not allow disk persistence, or a different type of persistence is required.
    17  // The use case that was in mind was AppEngine where datastore is the only persistence option.
    18  type TokenLoaderSaver interface {
    19  	SaveTokens(t AccessTokenResponse) error
    20  	LoadAccessAndRefreshToken() (AccessTokenResponse, error)
    21  }
    22  
    23  // SaveTokens will check for a provided 'TokenManager' interface
    24  // if one exists it will use its provided method
    25  func (z Zoho) SaveTokens(t AccessTokenResponse) error {
    26  	if z.tokenManager != nil {
    27  		return z.tokenManager.SaveTokens(t)
    28  	}
    29  
    30  	// Save the token response as GOB to file
    31  	file, err := os.OpenFile(z.tokensFile, os.O_WRONLY|os.O_CREATE, 0666)
    32  	if err != nil {
    33  		return fmt.Errorf("Failed to open file '%s': %s", z.tokensFile, err)
    34  	}
    35  	enc := gob.NewEncoder(file)
    36  
    37  	v := TokenWrapper{
    38  		Token: z.oauth.token,
    39  	}
    40  	v.SetExpiry()
    41  
    42  	err = enc.Encode(v)
    43  	if err != nil {
    44  		return fmt.Errorf("Failed to encode tokens to file '%s': %s", z.tokensFile, err)
    45  	}
    46  
    47  	return nil
    48  }
    49  
    50  // LoadAccessAndRefreshToken will check for a provided 'TokenManager' interface
    51  // if one exists it will use its provided method
    52  func (z Zoho) LoadAccessAndRefreshToken() (AccessTokenResponse, error) {
    53  	if z.tokenManager != nil {
    54  		return z.tokenManager.LoadAccessAndRefreshToken()
    55  	}
    56  
    57  	// Load the GOB and decode to AccessToken
    58  	file, err := os.OpenFile(z.tokensFile, os.O_RDONLY|os.O_CREATE, 0666)
    59  	if err != nil {
    60  		return AccessTokenResponse{}, fmt.Errorf("Failed to open file '%s': %s", z.tokensFile, err)
    61  	}
    62  	dec := gob.NewDecoder(file)
    63  
    64  	var v TokenWrapper
    65  	err = dec.Decode(&v)
    66  	if err != nil {
    67  		return AccessTokenResponse{}, fmt.Errorf("Failed to decode tokens from file '%s': %s", z.tokensFile, err)
    68  	}
    69  
    70  	if v.CheckExpiry() {
    71  		return v.Token, ErrTokenExpired
    72  	}
    73  
    74  	return v.Token, nil
    75  }
    76  
    77  // ErrTokenExpired should be returned when the token is expired but still exists in persistence
    78  var ErrTokenExpired = errors.New("zoho: oAuth2 token already expired")
    79  
    80  // ErrTokenInvalidCode is turned when the autorization code in a request is invalid
    81  var ErrTokenInvalidCode = errors.New("zoho: authorization-code is invalid ")
    82  
    83  // ErrClientSecretInvalidCode is turned when the client secret used is invalid
    84  var ErrClientSecretInvalidCode = errors.New("zoho: client secret used in authorization is invalid")
    85  
    86  // TokenWrapper should be used to provide the time.Time corresponding to the expiry of an access token
    87  type TokenWrapper struct {
    88  	Token   AccessTokenResponse
    89  	Expires time.Time
    90  }
    91  
    92  // SetExpiry sets the TokenWrappers expiry time to now + seconds until expiry
    93  func (t *TokenWrapper) SetExpiry() {
    94  	t.Expires = time.Now().Add(time.Duration(t.Token.ExpiresIn) * time.Second)
    95  }
    96  
    97  // CheckExpiry if the token expired before this instant
    98  func (t *TokenWrapper) CheckExpiry() bool {
    99  	return t.Expires.Before(time.Now())
   100  }
   101  
   102  func (z *Zoho) CheckForSavedTokens() error {
   103  	t, err := z.LoadAccessAndRefreshToken()
   104  	z.oauth.token = t
   105  
   106  	if err != nil && err == ErrTokenExpired {
   107  		return err
   108  	}
   109  
   110  	if (t != AccessTokenResponse{}) && err != ErrTokenExpired {
   111  		return nil
   112  	}
   113  	return fmt.Errorf("No saved tokens")
   114  }
   115  
   116  // DatastoreManager is an example TokenManager that satisfies the TokenManager interface
   117  // When instantiating, user must provide the *http.Request for the current app engine request
   118  // and the token key where the tokens are to be saved to/loaded from.
   119  type DatastoreManager struct {
   120  	Request         *http.Request
   121  	EntityNamespace string
   122  	TokensKey       string
   123  }
   124  
   125  // LoadAccessAndRefreshToken will use datastore package to get tokens from the datastore under the entity namespace
   126  // 'ZohoAccessTokens' unless a value is provided to the EntityNamespace field
   127  func (d DatastoreManager) LoadAccessAndRefreshToken() (AccessTokenResponse, error) {
   128  	t := TokenWrapper{}
   129  	if d.Request == nil || d.TokensKey == "" {
   130  		return AccessTokenResponse{}, fmt.Errorf("Must provide the *http.Request for the current request and a valid token key")
   131  	}
   132  
   133  	entity := "ZohoAccessTokens"
   134  	if d.EntityNamespace != "" {
   135  		entity = d.EntityNamespace
   136  	}
   137  
   138  	ctx := appengine.NewContext(d.Request)
   139  	k := datastore.NewKey(ctx, entity, d.TokensKey, 0, nil)
   140  
   141  	if err := datastore.Get(ctx, k, &t); err != nil {
   142  		return AccessTokenResponse{}, fmt.Errorf("Failed to retrieve tokens from datastore: %s", err)
   143  	}
   144  
   145  	if t.CheckExpiry() {
   146  		return AccessTokenResponse{}, ErrTokenExpired
   147  	}
   148  
   149  	return t.Token, nil
   150  }
   151  
   152  // SaveTokens will use datastore package to put tokens to the datastore under the entity namespace
   153  // 'ZohoAccessTokens' unless a value is provided to the EntityNamespace field
   154  func (d DatastoreManager) SaveTokens(t AccessTokenResponse) error {
   155  	if d.Request == nil || d.TokensKey == "" {
   156  		return fmt.Errorf("Must provide the *http.Request for the current request and a valid token key")
   157  	}
   158  
   159  	entity := "ZohoAccessTokens"
   160  	if d.EntityNamespace != "" {
   161  		entity = d.EntityNamespace
   162  	}
   163  
   164  	ctx := appengine.NewContext(d.Request)
   165  	k := datastore.NewKey(ctx, entity, d.TokensKey, 0, nil)
   166  
   167  	v := TokenWrapper{
   168  		Token: t,
   169  	}
   170  	v.SetExpiry()
   171  
   172  	if _, err := datastore.Put(ctx, k, v); err != nil {
   173  		return fmt.Errorf("Failed to save tokens to datastore: %s", err)
   174  	}
   175  
   176  	return nil
   177  }