134 lines
6.7 KiB
PHP
134 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\IntegrationSetting;
|
|
use App\Models\User;
|
|
use App\Services\Parcel2Go;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
class Parcel2GoOAuthTest extends TestCase
|
|
{
|
|
use LazilyRefreshDatabase;
|
|
|
|
public function test_only_an_administrator_can_start_account_consent(): void
|
|
{
|
|
$this->configureParcel2Go();
|
|
Http::preventStrayRequests();
|
|
|
|
$this->post(route('parcel2go.connect'))->assertRedirect(route('login'));
|
|
$this->actingAs(User::factory()->create())->post(route('parcel2go.connect'))->assertForbidden();
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_consent_uses_pkce_and_exact_callback_url(): void
|
|
{
|
|
$this->configureParcel2Go();
|
|
Http::preventStrayRequests();
|
|
|
|
$response = $this->actingAs(User::factory()->create(['role' => 'admin']))->post(route('parcel2go.connect'));
|
|
$query = [];
|
|
parse_str(parse_url($response->headers->get('Location'), PHP_URL_QUERY), $query);
|
|
$pending = session('parcel2go_oauth');
|
|
|
|
$this->assertSame('https://auth.example.test/connect/authorize', strtok($response->headers->get('Location'), '?'));
|
|
$this->assertSame('profile-client', $query['client_id']);
|
|
$this->assertSame(route('parcel2go.callback'), $query['redirect_uri']);
|
|
$this->assertSame('public-api my-profile offline_access', $query['scope']);
|
|
$this->assertSame('S256', $query['code_challenge_method']);
|
|
$this->assertSame($pending['state'], $query['state']);
|
|
$this->assertSame(rtrim(strtr(base64_encode(hash('sha256', $pending['verifier'], true)), '+/', '-_'), '='), $query['code_challenge']);
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_callback_rejects_bad_state_without_exchanging_a_code(): void
|
|
{
|
|
$this->configureParcel2Go();
|
|
Http::preventStrayRequests();
|
|
|
|
$this->actingAs(User::factory()->create(['role' => 'admin']))
|
|
->withSession(['parcel2go_oauth' => ['state' => 'expected', 'verifier' => 'verifier', 'started' => time()]])
|
|
->get(route('parcel2go.callback', ['state' => 'wrong', 'code' => 'code']))
|
|
->assertRedirect(route('settings.index'))->assertSessionHasErrors('parcel2go');
|
|
$this->assertDatabaseMissing('integration_settings', ['provider' => 'parcel2go_oauth']);
|
|
Http::assertNothingSent();
|
|
}
|
|
|
|
public function test_callback_stores_encrypted_tokens_and_uses_them_for_account_history(): void
|
|
{
|
|
$this->configureParcel2Go();
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://auth.example.test/connect/token' => Http::response([
|
|
'access_token' => 'profile-token', 'refresh_token' => 'refresh-token', 'expires_in' => 3600,
|
|
]),
|
|
'https://shipping.example.test/api/me/orders/detail' => Http::response(['Orders' => [['OrderLineId' => 123]]]),
|
|
]);
|
|
|
|
$this->actingAs(User::factory()->create(['role' => 'admin']))
|
|
->withSession(['parcel2go_oauth' => ['state' => 'expected', 'verifier' => 'verifier', 'started' => time()]])
|
|
->get(route('parcel2go.callback', ['state' => 'expected', 'code' => 'code']))
|
|
->assertRedirect(route('settings.index'))->assertSessionHas('status');
|
|
$connection = IntegrationSetting::findOrFail('parcel2go_oauth');
|
|
$this->assertSame('profile-token', $connection->settings['access_token']);
|
|
$this->assertStringNotContainsString('profile-token', $connection->getRawOriginal('settings'));
|
|
Http::assertSent(fn ($request) => $request->url() === 'https://auth.example.test/connect/token'
|
|
&& $request['grant_type'] === 'authorization_code' && $request['code_verifier'] === 'verifier');
|
|
$this->assertSame([['OrderLineId' => 123]], app(Parcel2Go::class)->fetch('me/orders/detail')['Orders']);
|
|
Http::assertSent(fn ($request) => $request->url() === 'https://shipping.example.test/api/me/orders/detail'
|
|
&& $request->hasHeader('Authorization', 'Bearer profile-token'));
|
|
}
|
|
|
|
public function test_expired_account_token_is_refreshed_before_history_request(): void
|
|
{
|
|
$this->configureParcel2Go();
|
|
IntegrationSetting::create(['provider' => 'parcel2go_oauth', 'settings' => [
|
|
'client_id' => 'profile-client', 'access_token' => 'old-token',
|
|
'refresh_token' => 'old-refresh', 'expires_at' => now()->subMinute()->toIso8601String(),
|
|
]]);
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://auth.example.test/connect/token' => Http::response([
|
|
'access_token' => 'new-token', 'refresh_token' => 'new-refresh', 'expires_in' => 3600,
|
|
]),
|
|
'https://shipping.example.test/api/me/orders/detail' => Http::response(['Orders' => []]),
|
|
]);
|
|
|
|
$this->assertSame([], app(Parcel2Go::class)->fetch('me/orders/detail')['Orders']);
|
|
$this->assertSame('new-refresh', IntegrationSetting::findOrFail('parcel2go_oauth')->settings['refresh_token']);
|
|
Http::assertSent(fn ($request) => $request->url() === 'https://auth.example.test/connect/token'
|
|
&& $request['grant_type'] === 'refresh_token' && $request['refresh_token'] === 'old-refresh');
|
|
Http::assertSent(fn ($request) => $request->url() === 'https://shipping.example.test/api/me/orders/detail'
|
|
&& $request->hasHeader('Authorization', 'Bearer new-token'));
|
|
}
|
|
|
|
public function test_callback_does_not_save_a_token_without_history_permission(): void
|
|
{
|
|
$this->configureParcel2Go();
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://auth.example.test/connect/token' => Http::response(['access_token' => 'limited-token', 'expires_in' => 3600]),
|
|
'https://shipping.example.test/api/me/orders/detail' => Http::response([], 403),
|
|
]);
|
|
|
|
$this->actingAs(User::factory()->create(['role' => 'admin']))
|
|
->withSession(['parcel2go_oauth' => ['state' => 'expected', 'verifier' => 'verifier', 'started' => time()]])
|
|
->get(route('parcel2go.callback', ['state' => 'expected', 'code' => 'code']))
|
|
->assertRedirect(route('settings.index'))->assertSessionHasErrors('parcel2go');
|
|
$this->assertDatabaseMissing('integration_settings', ['provider' => 'parcel2go_oauth']);
|
|
}
|
|
|
|
private function configureParcel2Go(): void
|
|
{
|
|
config()->set('services.parcel2go', [
|
|
'client_id' => 'basic-client', 'client_secret' => 'basic-secret',
|
|
'profile_client_id' => 'profile-client',
|
|
'auth_url' => 'https://auth.example.test/connect/token',
|
|
'base_url' => 'https://shipping.example.test/api',
|
|
'scope' => 'public-api',
|
|
]);
|
|
}
|
|
}
|