the browser-facing portion of osu!
at master 253 lines 8.2 kB view raw
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\Models\OAuth; 7 8use App\Events\UserSessionEvent; 9use App\Exceptions\InvalidScopeException; 10use App\Models\OAuth\Client; 11use App\Models\OAuth\Token; 12use App\Models\User; 13use Database\Factories\OAuth\RefreshTokenFactory; 14use Illuminate\Support\Facades\Event; 15use Laravel\Passport\Passport; 16use Tests\TestCase; 17 18class TokenTest extends TestCase 19{ 20 public function testAuthCodeChatWriteAllowsSelf() 21 { 22 $user = User::factory()->create(); 23 $client = Client::factory()->create(['user_id' => $user]); 24 25 $token = $this->createToken($user, ['chat.write'], $client); 26 $this->actAsUserWithToken($token); 27 28 $this->assertTrue($user->is($token->getResourceOwner())); 29 $this->assertTrue($user->is(auth()->user())); 30 } 31 32 /** 33 * @dataProvider authCodeChatWriteRequiresBotGroupDataProvider 34 */ 35 public function testAuthCodeChatWriteRequiresBotGroup(?string $group, ?string $expectedException) 36 { 37 $user = User::factory()->withGroup($group)->create(); 38 $client = Client::factory()->create(['user_id' => $user]); 39 $tokenUser = User::factory()->create(); 40 41 if ($expectedException !== null) { 42 $this->expectException($expectedException); 43 } else { 44 $this->expectNotToPerformAssertions(); 45 } 46 47 $this->createToken($tokenUser, ['chat.write'], $client); 48 } 49 50 public function testClientCredentialsRequiredForDelegation() 51 { 52 $user = User::factory()->create(); 53 $client = Client::factory()->create(['user_id' => $user]); 54 55 $this->expectException(InvalidScopeException::class); 56 $this->createToken($user, ['delegate'], $client); 57 } 58 59 public function testClientCredentialResourceOwnerBot() 60 { 61 $user = User::factory()->withGroup('bot')->create(); 62 $client = Client::factory()->create(['user_id' => $user]); 63 $token = $this->createToken(null, ['delegate'], $client); 64 65 $this->actAsUserWithToken($token); 66 67 $this->assertTrue($token->isClientCredentials()); 68 $this->assertTrue($user->is($token->getResourceOwner())); 69 $this->assertTrue($user->is(auth()->user())); 70 } 71 72 public function testClientCredentialResourceOwnerPublic() 73 { 74 $user = User::factory()->withGroup('bot')->create(); 75 $client = Client::factory()->create(['user_id' => $user]); 76 $token = $this->createToken(null, ['public'], $client); 77 78 $this->actAsUserWithToken($token); 79 80 $this->assertTrue($token->isClientCredentials()); 81 $this->assertNull($token->getResourceOwner()); 82 $this->assertNull(auth()->user()); 83 } 84 85 /** 86 * @dataProvider delegationNotAllowedScopesDataProvider 87 */ 88 public function testDelegationNotAllowedScopes(array $scopes) 89 { 90 $user = User::factory()->create(); 91 $client = Client::factory()->create(['user_id' => $user]); 92 93 $this->expectException(InvalidScopeException::class); 94 $this->createToken(null, $scopes, $client); 95 } 96 97 /** 98 * @dataProvider delegationRequiredScopesDataProvider 99 */ 100 public function testDelegationRequiredScopes(array $scopes, ?string $expectedException) 101 { 102 $user = User::factory()->withGroup('bot')->create(); 103 $client = Client::factory()->create(['user_id' => $user]); 104 105 if ($expectedException !== null) { 106 $this->expectException($expectedException); 107 } else { 108 $this->expectNotToPerformAssertions(); 109 } 110 111 $this->createToken(null, $scopes, $client); 112 } 113 114 /** 115 * @dataProvider delegationRequiresChatBotDataProvider 116 */ 117 public function testDelegationRequiresChatBot(?string $group, ?string $expectedException) 118 { 119 $user = User::factory()->withGroup($group)->create(); 120 $client = Client::factory()->create(['user_id' => $user]); 121 $tokenUser = User::factory()->create(); 122 123 if ($expectedException !== null) { 124 $this->expectException($expectedException); 125 } else { 126 $this->expectNotToPerformAssertions(); 127 } 128 129 $this->createToken(null, ['delegate'], $client); 130 } 131 132 /** 133 * @dataProvider scopesDataProvider 134 * 135 * @return void 136 */ 137 public function testScopes($scopes, $expectedException) 138 { 139 $user = User::factory()->create(); 140 $client = Client::factory()->create(['user_id' => $user]); 141 142 if ($expectedException !== null) { 143 $this->expectException($expectedException); 144 } else { 145 $this->expectNotToPerformAssertions(); 146 } 147 148 $this->createToken($user, $scopes, $client); 149 } 150 151 public function testScopesAreSorted() 152 { 153 $token = new Token(); 154 $token->scopes = ['i', 'am', 'a', 'scope']; 155 156 $this->assertSame(['a', 'am', 'i', 'scope'], $token->scopes); 157 } 158 159 /** 160 * @dataProvider scopesClientCredentialsDataProvider 161 * 162 * @return void 163 */ 164 public function testScopesClientCredentials($scopes, $expectedException) 165 { 166 $user = User::factory()->create(); 167 $client = Client::factory()->create(['user_id' => $user]); 168 169 if ($expectedException !== null) { 170 $this->expectException($expectedException); 171 } else { 172 $this->expectNotToPerformAssertions(); 173 } 174 175 $this->createToken(null, $scopes, $client); 176 } 177 178 public function testRevokeRecursive() 179 { 180 Event::fake(); 181 182 $refreshToken = (new RefreshTokenFactory())->create(); 183 $token = $refreshToken->accessToken; 184 185 $this->assertFalse($refreshToken->revoked); 186 $this->assertFalse($token->revoked); 187 188 $token->revokeRecursive(); 189 190 $this->assertTrue($refreshToken->fresh()->revoked); 191 $this->assertTrue($token->fresh()->revoked); 192 Event::assertDispatched(UserSessionEvent::class, fn (UserSessionEvent $event) => $event->action === 'logout'); 193 } 194 195 public static function authCodeChatWriteRequiresBotGroupDataProvider() 196 { 197 return [ 198 [null, InvalidScopeException::class], 199 ['admin', InvalidScopeException::class], 200 ['bng', InvalidScopeException::class], 201 ['bot', null], 202 ['gmt', InvalidScopeException::class], 203 ['nat', InvalidScopeException::class], 204 ]; 205 } 206 207 public static function delegationNotAllowedScopesDataProvider() 208 { 209 return Passport::scopes() 210 ->pluck('id') 211 ->filter(fn ($id) => !in_array($id, ['chat.write', 'delegate'], true)) 212 ->map(fn ($id) => [['delegate', $id]]) 213 ->values(); 214 } 215 216 public static function delegationRequiredScopesDataProvider() 217 { 218 return [ 219 'chat.write requires delegation' => [['chat.write'], InvalidScopeException::class], 220 'chat.write delegation' => [['chat.write', 'delegate'], null], 221 ]; 222 } 223 224 public static function delegationRequiresChatBotDataProvider() 225 { 226 return [ 227 [null, InvalidScopeException::class], 228 ['admin', InvalidScopeException::class], 229 ['bng', InvalidScopeException::class], 230 ['bot', null], 231 ['gmt', InvalidScopeException::class], 232 ['nat', InvalidScopeException::class], 233 ]; 234 } 235 236 public static function scopesDataProvider() 237 { 238 return [ 239 'null is not a valid scope' => [null, InvalidScopeException::class], 240 'empty scope should fail' => [[], InvalidScopeException::class], 241 'all scope is allowed' => [['*'], null], 242 ]; 243 } 244 245 public static function scopesClientCredentialsDataProvider() 246 { 247 return [ 248 'null is not a valid scope' => [null, InvalidScopeException::class], 249 'empty scope should fail' => [[], InvalidScopeException::class], 250 'all scope is not allowed' => [['*'], InvalidScopeException::class], 251 ]; 252 } 253}