Skip to content

Commit

Permalink
Merge pull request #33 from tonypartridger/main
Browse files Browse the repository at this point in the history
Add token encryption capabilties
  • Loading branch information
dcblogdev authored Mar 1, 2024
2 parents c4688d6 + 4bf9844 commit 6192d9f
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 22 deletions.
7 changes: 6 additions & 1 deletion config/xero.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@
* Set the scopes
*/
'scopes' => env('XERO_SCOPES', 'openid email profile offline_access accounting.settings accounting.transactions accounting.contacts'),
];

/**
* Encrypt tokens in database?
*/
'encrypt' => env('XERO_ENCRYPT', false),
];
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ You can publish the config file with:
php artisan vendor:publish --provider="Dcblogdev\Xero\XeroServiceProvider" --tag="config"
```

# Encrypt Tokens at rest
You can enable token encryption at rest by setting the following in your .env file:

```
XERO_ENCRYPT=true
```
this will use the native Laravel Crypt library to ensure the tokens are encrypted at rest.

# Migration

You can publish the migration with:
Expand Down
30 changes: 27 additions & 3 deletions src/Console/Commands/XeroShowAllCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Illuminate\Console\Command;
use Dcblogdev\Xero\Models\XeroToken;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;

class XeroShowAllCommand extends Command
{
Expand Down Expand Up @@ -33,12 +35,34 @@ public function handle()
'tenant_id',
'updated_at',
];

// Fetch all access tokens
$tokens = XeroToken::select($dataToDisplay)->get()->toArray();
$tokens = XeroToken::select($dataToDisplay)->get();

if(config('xero.encrypt')) {
$tokens->map(function($token) {
try {
$access_token = Crypt::decryptString($token->access_token);
} catch (DecryptException $e) {
$access_token = $token->access_token;
$refresh_token = $token->refresh_token;
}
// Split them as a refresh token may not exist...
try {
$refresh_token = Crypt::decryptString($token->refresh_token);
} catch (DecryptException $e) {
$refresh_token = $token->refresh_token;
}

$token->access_token = $access_token;
$token->refresh_token = $refresh_token;
return $token;
});
}

$this->table(
$dataToDisplay,
$tokens
$tokens->toArray()
);
}
}
}
2 changes: 1 addition & 1 deletion src/Facades/Xero.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Xero extends Facade
*
* @return string
*/
protected static function getFacadeAccessor()
protected static function getFacadeAccessor(): string
{
return 'xero';
}
Expand Down
48 changes: 34 additions & 14 deletions src/Xero.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
use Dcblogdev\Xero\Resources\Invoices;
use Dcblogdev\Xero\Resources\Webhooks;
use Exception;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;
use RuntimeException;

Expand Down Expand Up @@ -74,16 +76,7 @@ public function disconnect(): void
public function connect(): RedirectResponse|Application|Redirector
{
//when no code param redirect to Microsoft
if (! request()->has('code')) {
$url = self::$authorizeUrl . '?' . http_build_query([
'response_type' => 'code',
'client_id' => config('xero.clientId'),
'redirect_uri' => config('xero.redirectUri'),
'scope' => config('xero.scopes')
]);

return redirect()->away($url);
} elseif (request()->has('code')) {
if (request()->has('code')) {
// With the authorization code, we can retrieve access tokens and other data.
try {
$params = [
Expand Down Expand Up @@ -120,15 +113,42 @@ public function connect(): RedirectResponse|Application|Redirector
throw new Exception($e->getMessage());
}
}

$url = self::$authorizeUrl . '?' . http_build_query([
'response_type' => 'code',
'client_id' => config('xero.clientId'),
'redirect_uri' => config('xero.redirectUri'),
'scope' => config('xero.scopes')
]);

return redirect()->away($url);
}

public function getTokenData(): XeroToken|null
{
if ($this->tenant_id) {
return XeroToken::where('tenant_id', '=', $this->tenant_id)->first();
$token = XeroToken::where('tenant_id', '=', $this->tenant_id)->first();
} else {
$token = XeroToken::first();
}

if(config('xero.encrypt')) {
try {
$access_token = Crypt::decryptString($token->access_token);
} catch (DecryptException $e) {
$access_token = $token->access_token;
}
// Split them as a refresh token may not exist...
try {
$refresh_token = Crypt::decryptString($token->refresh_token);
} catch (DecryptException $e) {
$refresh_token = $token->refresh_token;
}
$token->access_token = $access_token;
$token->refresh_token = $refresh_token;
}

return XeroToken::first();
return $token;
}

public function getAccessToken($redirectWhenNotConnected = true): string
Expand Down Expand Up @@ -222,10 +242,10 @@ protected function storeToken($token, $tenantData = null)
{
$data = [
'id_token' => $token['id_token'],
'access_token' => $token['access_token'],
'access_token' => config('xero.encrypt') ? Crypt::encryptString($token['access_token']) : $token['access_token'],
'expires_in' => $token['expires_in'],
'token_type' => $token['token_type'],
'refresh_token' => $token['refresh_token'],
'refresh_token' => config('xero.encrypt') ? Crypt::encryptString($token['refresh_token']) : $token['refresh_token'],
'scopes' => $token['scope']
];

Expand Down
4 changes: 2 additions & 2 deletions src/database/migrations/create_xero_tokens_table.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateXeroTokensTable extends Migration
{
Expand Down
36 changes: 35 additions & 1 deletion tests/XeroTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
use Dcblogdev\Xero\Models\XeroToken;
use Dcblogdev\Xero\Xero;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;

beforeEach(function () {
Expand Down Expand Up @@ -114,4 +116,36 @@
$data = XeroFacade::getAccessToken();

expect($data)->toBe('1234');
});
});

test('can get tokens when not-encrypted but encryption is enabled', function () {

Config::set('xero.encrypt', true);

XeroToken::create([
'id' => 0,
'access_token' => '1234',
'expires_in' => strtotime('+1 day'),
'scopes' => 'contacts'
]);

$data = XeroFacade::getAccessToken();

expect($data)->toBe('1234');
});

test('can get tokens when encrypted', function () {

Config::set('xero.encrypt', true);

XeroToken::create([
'id' => 0,
'access_token' => Crypt::encryptString('1234'),
'expires_in' => strtotime('+1 day'),
'scopes' => 'contacts'
]);

$data = XeroFacade::getAccessToken();

expect($data)->toBe('1234');
});

0 comments on commit 6192d9f

Please sign in to comment.