Skip to content

Commit 2112c8f

Browse files
committed
V2.0.0 Updates
1 parent 7a8df93 commit 2112c8f

File tree

7 files changed

+448
-132
lines changed

7 files changed

+448
-132
lines changed

CHANGELOG.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,44 @@
22

33
All notable changes to `laravel-xero-oauth2` will be documented in this file
44

5-
## 1.0.0 - 201X-XX-XX
5+
## v2.0.0 Release
6+
- Make most resolution of credentials a binding instead of a singleton to fix queued jobs #27
7+
- Switch OauthCredentialProvider to be an interface to allow for custom storage #8
8+
- Make old OauthCredentialProvider `\Webfox\Xero\Oauth2CredentialManagers\CacheStore`
9+
- Added new `Webfox\Xero\Oauth2CredentialManagers\FileStore` as default
10+
- Added ability to skip `route` helper for redirect_uri #10
11+
12+
## v1.3.1 Release
13+
14+
- Fix bug in Webhook.php #25
15+
16+
## v1.3.0 Release
17+
18+
- Remove PHP 7.4 typehint from webhook #17
19+
- Upgrade to v2 of the Xero API #16
20+
21+
## v1.2.1 Release
22+
23+
- Specify scope separator in Oauth provider #15
24+
25+
## v1.2.0 Release
26+
27+
- Fix issue of credentials being forgotten #11 #10 #3
28+
- Updates to the readme
29+
- Add accounting.settings as a default scope #12
30+
31+
## v 1.1.2
32+
33+
- Added support for Laravel 7
34+
35+
## v1.1.1
36+
37+
- Remove PHP 7.4 features to maintain 7.3 compatibility
38+
39+
## V1.1.0 Release
40+
41+
- Added webhook support
42+
43+
## v1.0.0 Release
644

745
- initial release

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ namespace App\Http\Controllers;
5151

5252
use Illuminate\Http\Request;
5353
use App\Http\Controllers\Controller;
54-
use Webfox\Xero\OauthCredentialManager;
54+
use Webfox\Xero\CacheStore;
5555

5656
class XeroController extends Controller
5757
{
5858

59-
public function index(Request $request, OauthCredentialManager $xeroCredentials)
59+
public function index(Request $request, CacheStore $xeroCredentials)
6060
{
6161
try {
6262
// Check if we've got any stored credentials
@@ -122,6 +122,30 @@ class XeroController extends Controller
122122
Route::get('/manage/xero', [\App\Http\Controllers\XeroController::class, 'index'])->name('xero.auth.success');
123123
```
124124

125+
## Credential Storage
126+
Version 1 of this package stored the credentials in the cache, while this worked for most, some users clear their cache
127+
on deployment of a new version of their app, this also restricted an app to only one set of credentials per codebase.
128+
129+
Version 2 swaps this out for an interface and provides a default `FileStore` implementation which stores the credentials on
130+
`/storage/framework/xero.json` you can switch out the credential store (e.g. for your own `UserStore` if you wanted to store
131+
the credentials against your user) in one of two ways
132+
133+
1. If it's a simple store and Laravel can automatically resolve your bindings, simply change the `xero.credential_store` config
134+
key to point to your new implementation.
135+
2. If it requires more advanced logic (e.g. using the current user to retrieve the credentials) then you can rebind this
136+
in your `AppServiceProvider` or a Middleware
137+
e.g.
138+
139+
```php
140+
$this->app->bind(OauthCredentialManager::class, function(Application $app) {
141+
return new UserStorageProvider(
142+
\Auth::user(), // Storage Mechanism
143+
$app->make('session.store'), // Used for storing/retrieving oauth 2 "state" for redirects
144+
$app->make(\Webfox\Xero\Oauth2Provider::class) // Used for getting redirect url and refreshing token
145+
);
146+
});
147+
```
148+
125149
## Using Webhooks
126150
On your application in the Xero developer portal create a webhook to get your webhook key.
127151

@@ -141,7 +165,6 @@ namespace App\Http\Controllers;
141165
use Webfox\Xero\Webhook;
142166
use Illuminate\Http\Request;
143167
use Illuminate\Http\Response;
144-
use Webfox\Xero\WebhookEvent;
145168
use XeroApi\XeroPHP\Models\Accounting\Contact;
146169
use XeroApi\XeroPHP\Models\Accounting\Invoice;
147170

config/config.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
<?php
22

3+
use Webfox\Xero\Oauth2CredentialManagers\FileStore;
4+
35
return [
46

57
'api_host' => 'https://api.xero.com/api.xro/2.0',
68

9+
/************************************************************************
10+
* Class used to store credentials.
11+
* Must implement OauthCredentialManager Interface
12+
************************************************************************/
13+
'credential_store' => FileStore::class,
14+
715
'oauth' => [
816
/************************************************************************
917
* Client ID provided by Xero when registering your application
@@ -35,7 +43,7 @@
3543
/************************************************************************
3644
* Url to redirect to upon success
3745
************************************************************************/
38-
'redirect_on_success' => 'xero.auth.success',
46+
'redirect_on_success' => 'xero.auth.success',
3947

4048
/************************************************************************
4149
* Url for Xero to redirect to upon granting access
@@ -44,6 +52,12 @@
4452
************************************************************************/
4553
'redirect_uri' => 'xero.auth.callback',
4654

55+
/************************************************************************
56+
* If the 'redirect_uri' is not a route name, but rather a full url set
57+
* this to true and we won't wrap it in `route()`
58+
************************************************************************/
59+
'redirect_full_url' => false,
60+
4761
/************************************************************************
4862
* Urls for Xero's Oauth integration, you shouldn't need to change these
4963
************************************************************************/
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
4+
namespace Webfox\Xero\Oauth2CredentialManagers;
5+
6+
7+
use Illuminate\Session\Store;
8+
use Illuminate\Cache\Repository;
9+
use League\OAuth2\Client\Token\AccessTokenInterface;
10+
use Webfox\Xero\Oauth2Provider;
11+
use Webfox\Xero\OauthCredentialManager;
12+
13+
class CacheStore implements OauthCredentialManager
14+
{
15+
/** @var Repository */
16+
protected $cache;
17+
18+
/** @var Oauth2Provider */
19+
protected $oauthProvider;
20+
21+
/** @var Store */
22+
protected $session;
23+
24+
protected $cacheKey = 'xero_oauth';
25+
26+
public function __construct(Repository $cache, Store $session, Oauth2Provider $oauthProvider)
27+
{
28+
$this->cache = $cache;
29+
$this->oauthProvider = $oauthProvider;
30+
$this->session = $session;
31+
}
32+
33+
public function getAccessToken(): string
34+
{
35+
return $this->data('token');
36+
}
37+
38+
public function getRefreshToken(): string
39+
{
40+
return $this->data('refresh_token');
41+
}
42+
43+
public function getTenantId(): string
44+
{
45+
return $this->data('tenant_id');
46+
}
47+
48+
public function getExpires(): int
49+
{
50+
return $this->data('expires');
51+
}
52+
53+
public function getState(): string
54+
{
55+
return $this->session->get($this->cacheKey);
56+
}
57+
58+
public function getAuthorizationUrl(): string
59+
{
60+
$redirectUrl = $this->oauthProvider->getAuthorizationUrl(['scope' => config('xero.oauth.scopes')]);
61+
$this->session->put($this->cacheKey, $this->oauthProvider->getState());
62+
63+
return $redirectUrl;
64+
}
65+
66+
public function getData(): array
67+
{
68+
return $this->data();
69+
}
70+
71+
public function exists(): bool
72+
{
73+
return $this->cache->has($this->cacheKey);
74+
}
75+
76+
public function isExpired(): bool
77+
{
78+
return time() >= $this->data('expires');
79+
}
80+
81+
public function refresh(): void
82+
{
83+
$newAccessToken = $this->oauthProvider->getAccessToken('refresh_token', [
84+
'refresh_token' => $this->getRefreshToken(),
85+
]);
86+
87+
$this->store($newAccessToken);
88+
}
89+
90+
public function store(AccessTokenInterface $token, string $tenantId = null): void
91+
{
92+
$this->cache->forever($this->cacheKey, [
93+
'token' => $token->getToken(),
94+
'refresh_token' => $token->getRefreshToken(),
95+
'id_token' => $token->getValues()['id_token'],
96+
'expires' => $token->getExpires(),
97+
'tenant_id' => $tenantId ?? $this->getTenantId()
98+
]);
99+
}
100+
101+
public function getUser(): ?array
102+
{
103+
104+
try {
105+
$jwt = new \XeroAPI\XeroPHP\JWTClaims();
106+
$jwt->setTokenId($this->data('id_token'));
107+
$decodedToken = $jwt->decode();
108+
109+
return [
110+
'given_name' => $decodedToken->getGivenName(),
111+
'family_name' => $decodedToken->getFamilyName(),
112+
'email' => $decodedToken->getEmail(),
113+
'user_id' => $decodedToken->getXeroUserId(),
114+
'username' => $decodedToken->getPreferredUsername(),
115+
'session_id' => $decodedToken->getGlobalSessionId()
116+
];
117+
} catch (\Throwable $e) {
118+
return null;
119+
}
120+
}
121+
122+
protected function data($key = null)
123+
{
124+
if (!$this->exists()) {
125+
throw new \Exception('Xero oauth credentials are missing');
126+
}
127+
128+
$cacheData = $this->cache->get($this->cacheKey);
129+
return empty($key) ? $cacheData : ($cacheData[$key] ?? null);
130+
}
131+
}

0 commit comments

Comments
 (0)