1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace Tests\Controllers\OAuth;
7
8use App\Libraries\OAuth\EncodeToken;
9use App\Mail\UserVerification as UserVerificationMail;
10use App\Models\OAuth\Client;
11use App\Models\OAuth\Token;
12use App\Models\User;
13use Database\Factories\OAuth\ClientFactory;
14use Database\Factories\OAuth\RefreshTokenFactory;
15use Database\Factories\UserFactory;
16use Tests\TestCase;
17
18class TokensControllerTest extends TestCase
19{
20 public static function dataProviderForTestIssueTokenWithRefreshTokenInheritsVerified(): array
21 {
22 return [
23 [true],
24 [false],
25 ];
26 }
27
28 public function testDestroyCurrent()
29 {
30 $refreshToken = (new RefreshTokenFactory())->create();
31 $token = $refreshToken->accessToken;
32
33 $this
34 ->actingWithToken($token)
35 ->json('DELETE', route('api.oauth.tokens.current'))
36 ->assertSuccessful();
37
38 $this->assertTrue($token->fresh()->revoked);
39 $this->assertTrue($refreshToken->fresh()->revoked);
40 }
41
42 public function testDestroyCurrentClientGrant()
43 {
44 $token = Token::factory()->create(['user_id' => null]);
45
46 $this
47 ->actingWithToken($token)
48 ->json('DELETE', route('api.oauth.tokens.current'))
49 ->assertSuccessful();
50
51 $this->assertTrue($token->fresh()->revoked);
52 }
53
54 public function testIssueTokenWithPassword(): void
55 {
56 \Mail::fake();
57
58 $user = User::factory()->create();
59 $client = (new ClientFactory())->create([
60 'password_client' => true,
61 ]);
62
63 $this->expectCountChange(fn () => $user->tokens()->count(), 1);
64
65 $tokenJson = $this->json('POST', route('oauth.passport.token'), [
66 'grant_type' => 'password',
67 'client_id' => $client->getKey(),
68 'client_secret' => $client->secret,
69 'scope' => '*',
70 'username' => $user->username,
71 'password' => UserFactory::DEFAULT_PASSWORD,
72 ])->assertSuccessful()
73 ->decodeResponseJson();
74
75 $this->json('GET', route('api.me'), [], [
76 'Authorization' => "Bearer {$tokenJson['access_token']}",
77 ])->assertSuccessful()
78 ->assertJsonPath('session_verified', false);
79
80 // unverified access to api should trigger this but not necessarily return 401
81 \Mail::assertQueued(UserVerificationMail::class);
82 }
83
84 /**
85 * @dataProvider dataProviderForTestIssueTokenWithRefreshTokenInheritsVerified
86 */
87 public function testIssueTokenWithRefreshTokenInheritsVerified(bool $verified): void
88 {
89 \Mail::fake();
90
91 $client = Client::factory()->create(['password_client' => true]);
92 $accessToken = Token::factory()->create([
93 'client_id' => $client,
94 'scopes' => ['*'],
95 'verified' => $verified,
96 ]);
97 $refreshToken = (new RefreshTokenFactory())
98 ->create(['access_token_id' => $accessToken]);
99 $refreshTokenString = EncodeToken::encodeRefreshToken($refreshToken, $accessToken);
100 $user = $accessToken->user;
101
102 $this->expectCountChange(fn () => $user->tokens()->count(), 1);
103
104 $tokenJson = $this->json('POST', route('oauth.passport.token'), [
105 'grant_type' => 'refresh_token',
106 'client_id' => $client->getKey(),
107 'client_secret' => $client->secret,
108 'refresh_token' => $refreshTokenString,
109 'scope' => implode(' ', $accessToken->scopes),
110 ])->assertSuccessful()
111 ->decodeResponseJson();
112
113 $this->json('GET', route('api.me'), [], [
114 'Authorization' => "Bearer {$tokenJson['access_token']}",
115 ])->assertSuccessful()
116 ->assertJsonPath('session_verified', $verified);
117
118 \Mail::assertQueued(UserVerificationMail::class, $verified ? 0 : 1);
119 }
120}