diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 9f892563..8dc3cc85 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Laravel\Passport\Exceptions\MissingScopeException; use Throwable; class Handler extends ExceptionHandler @@ -30,6 +31,8 @@ class Handler extends ExceptionHandler if (Str::endsWith($model, 'Texture')) { $exception = new ModelNotFoundException(trans('skinlib.non-existent')); } + } elseif ($exception instanceof MissingScopeException) { + return json($exception->getMessage(), 403); } return parent::render($request, $exception); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d32a783e..47de09ab 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -65,5 +65,7 @@ class Kernel extends HttpKernel 'setup' => \App\Http\Middleware\CheckInstallation::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \App\Http\Middleware\CheckUserVerified::class, + 'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class, + 'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class, ]; } diff --git a/app/Models/Scope.php b/app/Models/Scope.php new file mode 100644 index 00000000..1f278925 --- /dev/null +++ b/app/Models/Scope.php @@ -0,0 +1,19 @@ +registerPolicies(); + + Passport::routes(); + + $defaultScopes = [ + 'User.Read' => 'auth.oauth.scope.user.read', + 'Notification.Read' => 'auth.oauth.scope.notification.read', + 'Notification.ReadWrite' => 'auth.oauth.scope.notification.readwrite', + 'Player.Read' => 'auth.oauth.scope.player.read', + 'Player.ReadWrite' => 'auth.oauth.scope.player.readwrite', + 'Closet.Read' => 'auth.oauth.scope.closet.read', + 'Closet.ReadWrtie' => 'auth.oauth.scope.closet.readwrite', + 'UsersManagement.Read' => 'auth.oauth.scope.users-management.read', + 'UsersManagement.ReadWrite' => 'auth.oauth.scope.users-management.readwrite', + 'PlayersManagement.Read' => 'auth.oauth.scope.players-management.read', + 'PlayersManagement.ReadWrite' => 'auth.oauth.scope.players-management.readwrite', + 'ClosetManagement.Read' => 'auth.oauth.scope.closet-management.read', + 'ClosetManagement.ReadWrite' => 'auth.oauth.scope.closet-management.readwrite', + 'ReportsManagement.Read' => 'auth.oauth.scope.reports-management.read', + 'ReportsManagement.ReadWrite' => 'auth.oauth.scope.reports-management.readwrite', + ]; + + $scopes = Scope::all()->pluck('description', 'name')->all(); + + Passport::tokensCan(array_merge($defaultScopes, $scopes)); + + Passport::setDefaultScope(['User.Read']); + } +} diff --git a/database/migrations/2021_04_14_181300_create_scope_table.php b/database/migrations/2021_04_14_181300_create_scope_table.php new file mode 100644 index 00000000..166dca10 --- /dev/null +++ b/database/migrations/2021_04_14_181300_create_scope_table.php @@ -0,0 +1,34 @@ +increments('id'); + $table->string('name')->unique(); + $table->string('description'); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('scopes'); + } +} diff --git a/resources/lang/en/auth.yml b/resources/lang/en/auth.yml index 71260b4e..583407e1 100644 --- a/resources/lang/en/auth.yml +++ b/resources/lang/en/auth.yml @@ -64,6 +64,31 @@ oauth: title: Authorization introduction: A 3rd-party application ":name" is requesting permission to access your account. button: Authorize + permissions: Permissions + scope: + user: + read: Sign you in and read your profile + notification: + read: Allows the app to read your notifications. + readwrite: Allows the app to send notifications. + player: + read: Allows the app to read your players. + readwrite: Allows the app to create, read, update and delete your players. + closet: + read: Allows the app to read your closet items. + readwrite: Allows the app to create, read, update and delete your closet items. + users-management: + read: Allows the app to read site's users. + readwrite: Allows the app to create, read, update and delete site's users. + players-management: + read: Allows the app to read site's players. + readwrite: Allows the app to create, read, update and delete site's players. + closet-management: + read: Allows the app to read user's of your site closet items. + readwrite: Allows the app to create, read, update and delete user's closet items. + reports-management: + read: Allows the app to read user's reports. + readwrite: Allows the app to read and review user's reports. email: Email register-link: Register a new account diff --git a/resources/views/vendor/passport/authorize.twig b/resources/views/vendor/passport/authorize.twig index daa83c12..7e73ad03 100644 --- a/resources/views/vendor/passport/authorize.twig +++ b/resources/views/vendor/passport/authorize.twig @@ -8,6 +8,18 @@

{{ trans('auth.oauth.authorization.introduction', {name: client.name}) }}

+ {% if scopes %} +
+

+ {{ trans('auth.oauth.authorization.permission') }} +

+ +
+ {% endif %}
{{ csrf_field() }} diff --git a/routes/api.php b/routes/api.php index 27d37186..10485593 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,34 +3,42 @@ Route::any('', 'HomeController@apiRoot'); Route::prefix('user')->middleware('auth:oauth')->group(function () { - Route::get('', 'UserController@user'); + Route::get('', 'UserController@user')->middleware(['scope:User.Read']); - Route::get('notifications', 'NotificationsController@all'); - Route::post('notifications/{id}', 'NotificationsController@read'); + Route::middleware(['scope:Notification.Read'])->group(function () { + Route::get('notifications', 'NotificationsController@all'); + Route::post('notifications/{id}', 'NotificationsController@read'); + }); }); Route::prefix('players')->middleware('auth:oauth')->group(function () { - Route::get('', 'PlayerController@list'); - Route::post('', 'PlayerController@add'); - Route::delete('{player}', 'PlayerController@delete'); - Route::put('{player}/name', 'PlayerController@rename'); - Route::put('{player}/textures', 'PlayerController@setTexture'); - Route::delete('{player}/textures', 'PlayerController@clearTexture'); + Route::get('', 'PlayerController@list')->middleware(['scope:Player.Read,Player.ReadWrite']); + + Route::middleware(['scope:Player.ReadWrite'])->group(function () { + Route::post('', 'PlayerController@add'); + Route::delete('{player}', 'PlayerController@delete'); + Route::put('{player}/name', 'PlayerController@rename'); + Route::put('{player}/textures', 'PlayerController@setTexture'); + Route::delete('{player}/textures', 'PlayerController@clearTexture'); + }); }); Route::prefix('closet')->middleware('auth:oauth')->group(function () { - Route::get('', 'ClosetController@getClosetData'); - Route::post('', 'ClosetController@add'); - Route::put('{tid}', 'ClosetController@rename'); - Route::delete('{tid}', 'ClosetController@remove'); + Route::get('', 'ClosetController@getClosetData')->middleware(['scope:Closet.Read,Closet.ReadWrite']); + + Route::middleware(['scope:Closet.ReadWrite'])->group(function () { + Route::post('', 'ClosetController@add'); + Route::put('{tid}', 'ClosetController@rename'); + Route::delete('{tid}', 'ClosetController@remove'); + }); }); Route::prefix('admin') ->middleware(['auth:oauth', 'role:admin']) ->group(function () { Route::prefix('users')->group(function () { - Route::get('', 'UsersManagementController@list')->name('list'); - Route::prefix('{user}')->group(function () { + Route::get('', 'UsersManagementController@list')->name('list')->middleware(['scope:UsersManagement.Read,UsersManagement.ReadWrite']); + Route::prefix('{user}')->middleware(['scope:UsersManagement.ReadWrite'])->group(function () { Route::put('email', 'UsersManagementController@email')->name('email'); Route::put('verification', 'UsersManagementController@verification')->name('verification'); Route::put('nickname', 'UsersManagementController@nickname')->name('nickname'); @@ -42,23 +50,28 @@ Route::prefix('admin') }); Route::prefix('players')->group(function () { - Route::get('', 'PlayersManagementController@list'); - Route::put('{player}/name', 'PlayersManagementController@name'); - Route::put('{player}/owner', 'PlayersManagementController@owner'); - Route::put('{player}/textures', 'PlayersManagementController@texture'); - Route::delete('{player}', 'PlayersManagementController@delete'); + Route::get('', 'PlayersManagementController@list')->middleware(['scope:PlayersManagement.Read,PlayersManagement.ReadWrite']); + + Route::middleware(['scope:PlayersManagement.ReadWrite'])->group(function () { + Route::put('{player}/name', 'PlayersManagementController@name'); + Route::put('{player}/owner', 'PlayersManagementController@owner'); + Route::put('{player}/textures', 'PlayersManagementController@texture'); + Route::delete('{player}', 'PlayersManagementController@delete'); + }); }); Route::prefix('closet')->group(function () { - Route::get('{user}', 'ClosetManagementController@list'); - Route::post('{user}', 'ClosetManagementController@add'); - Route::delete('{user}', 'ClosetManagementController@remove'); + Route::get('{user}', 'ClosetManagementController@list')->middleware(['scope:ClosetManagement.Read,ClosetManagement.ReadWrite']); + Route::middleware(['scope:ClosetManagement.ReadWrite'])->group(function () { + Route::post('{user}', 'ClosetManagementController@add'); + Route::delete('{user}', 'ClosetManagementController@remove'); + }); }); Route::prefix('reports')->group(function () { - Route::get('', 'ReportController@manage'); - Route::put('{report}', 'ReportController@review'); + Route::get('', 'ReportController@manage')->middleware(['scope:ReportsManagement.Read,ReportsManagement.ReadWrite']); + Route::put('{report}', 'ReportController@review')->middleware(['scope:ReportsManagement.ReadWrite']); }); - Route::post('notifications', 'NotificationsController@send'); + Route::post('notifications', 'NotificationsController@send')->middleware(['scope:Notification.ReadWrite']); });