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 }