the browser-facing portion of osu!
at master 6.4 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\Middleware; 7 8use App\Http\Middleware\RequireScopes; 9use App\Models\User; 10use Illuminate\Routing\Route; 11use Laravel\Passport\Exceptions\MissingScopeException; 12use Request; 13use Tests\TestCase; 14 15class RequireScopesTest extends TestCase 16{ 17 protected $next; 18 protected $request; 19 protected $user; 20 21 /** 22 * @dataProvider clientCredentialsTestDataProvider 23 */ 24 public function testClientCredentials($scopes, $expectedException) 25 { 26 $this->setRequest(['public']); 27 $this->setUser(null, ['public']); 28 29 app(RequireScopes::class)->handle($this->request, $this->next); 30 $this->assertTrue(oauth_token()->isClientCredentials()); 31 } 32 33 public function testClientCredentialsIsGuest() 34 { 35 $this->setRequest(['public']); 36 $this->setUser(null, ['public']); 37 38 app(RequireScopes::class)->handle($this->request, $this->next); 39 $this->assertNull(auth()->user()); 40 } 41 42 public function testClientCredentialsWhenAllScopeRequired() 43 { 44 $this->setRequest(); 45 $this->setUser(null, ['public']); 46 47 $this->expectException(MissingScopeException::class); 48 49 app(RequireScopes::class)->handle($this->request, $this->next); 50 $this->assertTrue(oauth_token()->isClientCredentials()); 51 } 52 53 public function testRequireScopesLayered() 54 { 55 $userScopes = ['identify']; 56 $requireScopes = ['identify']; 57 58 $this->setRequest($requireScopes); 59 $this->setUser($this->user, $userScopes); 60 61 app(RequireScopes::class)->handle($this->request, function () use ($requireScopes) { 62 app(RequireScopes::class)->handle($this->request, $this->next, ...$requireScopes); 63 }); 64 65 $this->assertTrue(!oauth_token()->isClientCredentials()); 66 } 67 68 public function testRequireScopesLayeredNoPermission() 69 { 70 $userScopes = ['somethingelse']; 71 $requireScopes = ['identify']; 72 73 $this->setRequest($requireScopes); 74 $this->setUser($this->user, $userScopes); 75 76 $this->expectException(MissingScopeException::class); 77 app(RequireScopes::class)->handle($this->request, function () use ($requireScopes) { 78 app(RequireScopes::class)->handle($this->request, $this->next, ...$requireScopes); 79 }); 80 } 81 82 public function testRequireScopesSkipped() 83 { 84 $userScopes = ['somethingelse']; 85 $requireScopes = ['identify']; 86 87 $this->setRequest($requireScopes, Request::create('/api/v2/changelog', 'GET')); 88 $this->setUser($this->user, $userScopes); 89 90 app(RequireScopes::class)->handle($this->request, $this->next, ...$requireScopes); 91 $this->assertTrue(!oauth_token()->isClientCredentials()); 92 } 93 94 /** 95 * @dataProvider userScopesTestDataProvider 96 */ 97 public function testUserScopes($requiredScopes, $userScopes, $expectedException) 98 { 99 $this->setRequest($requiredScopes); 100 $this->setUser($this->user, $userScopes); 101 102 if ($expectedException !== null) { 103 $this->expectException($expectedException); 104 } 105 106 if ($requiredScopes === null) { 107 app(RequireScopes::class)->handle($this->request, $this->next); 108 } else { 109 app(RequireScopes::class)->handle($this->request, $this->next, ...$requiredScopes); 110 } 111 112 $this->assertTrue(!oauth_token()->isClientCredentials()); 113 } 114 115 public static function clientCredentialsTestDataProvider() 116 { 117 return [ 118 'null is not a valid scope' => [null, MissingScopeException::class], 119 'empty scope should fail' => [[], MissingScopeException::class], 120 'public' => [['public'], null], 121 'all scope is not allowed' => [['*'], MissingScopeException::class], 122 ]; 123 } 124 125 public function clientCredentialsTestWhenAllScopeRequiredDataProvider() 126 { 127 return [ 128 'null is not a valid scope' => [null, MissingScopeException::class], 129 'empty scope should fail' => [[], MissingScopeException::class], 130 'public' => [['public'], MissingScopeException::class], 131 'all scope is not allowed' => [['*'], MissingScopeException::class], 132 ]; 133 } 134 135 public static function userScopesTestDataProvider() 136 { 137 return [ 138 'All scopes' => [null, ['*'], null], 139 'Has the required scope' => [['identify'], ['identify'], null], 140 'Does not have the required scope' => [['identify'], ['somethingelse'], MissingScopeException::class], 141 'Requires specific scope and all scope' => [['identify'], ['*'], null], 142 'Requires specific scope and multiple non-matching scopes' => [['identify'], ['somethingelse', 'alsonotright', 'nope'], MissingScopeException::class], 143 'Requires specific scope and multiple scopes' => [['identify'], ['somethingelse', 'identify', 'nope'], null], 144 'Blank require should deny regular scopes' => [null, ['identify'], MissingScopeException::class], 145 ]; 146 } 147 148 protected function setRequest(?array $scopes = null, $request = null) 149 { 150 $this->request = $request ?? Request::create('/api/_fake', 'GET'); 151 152 $this->next = static function () { 153 // just an empty closure. 154 }; 155 156 // so request() works 157 \Request::swap($this->request); 158 159 // set a fake route resolver 160 $this->request->setRouteResolver(function () use ($scopes) { 161 $route = new Route(['GET'], '/api/_fake', null); 162 $route->middleware('require-scopes'); 163 164 if ($scopes !== null) { 165 $route->middleware('require-scopes:'.implode(',', $scopes)); 166 } 167 168 return $route; 169 }); 170 } 171 172 protected function setUp(): void 173 { 174 parent::setUp(); 175 176 // nearly all the tests in the class need a user, so might as well set it up here. 177 $this->user = User::factory()->create(); 178 } 179 180 protected function setUser(?User $user, ?array $scopes = null, $client = null) 181 { 182 $token = $this->createToken($user, $scopes, $client); 183 $this->actAsUserWithToken($token); 184 } 185}