Skip to content

Commit

Permalink
Add manager role (#6)
Browse files Browse the repository at this point in the history
Manager role means read-only access to all devices within the
application. It's meant for network operators, becase they might find it
useful while debugging network issues.
  • Loading branch information
JanOppolzer authored Feb 7, 2024
1 parent 80b46e5 commit a4d4d18
Show file tree
Hide file tree
Showing 21 changed files with 442 additions and 7 deletions.
2 changes: 1 addition & 1 deletion app/Http/Controllers/DeviceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function index(): View
{
$this->authorize('viewAny', Device::class);

$types = auth()->user()->admin
$types = auth()->user()->admin || auth()->user()->manager
? Category::pluck('type')
: User::findOrFail(Auth::id())->categories()->pluck('type');

Expand Down
42 changes: 42 additions & 0 deletions app/Http/Controllers/UserSubroleController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Http\Controllers;

use App\Mail\UserSubroleChanged;
use App\Mail\YourSubroleChanged;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class UserSubroleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}

public function update(Request $request, User $user): RedirectResponse
{
$this->authorize('do-everything');

if ($request->user()->is($user)) {
return to_route('users.show', $user)
->with('status', __('users.cannot_toggle_your_role'))
->with('color', 'red');
}

$user->manager = $user->manager ? false : true;
$user->update();

Mail::send(new UserSubroleChanged($user));
Mail::send(new YourSubroleChanged($user));

$role = $user->manager ? 'managered' : 'demanagered';
$color = $user->manager ? 'indigo' : 'yellow';

return to_route('users.show', $user)
->with('status', __("users.{$role}", ['name' => $user->name]))
->with('color', $color);
}
}
2 changes: 1 addition & 1 deletion app/Http/Livewire/SearchDevices.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function render()
$sort = $this->sort ?: 'mac';
$order = strcasecmp($this->order, 'desc') === 0 ? 'DESC' : 'ASC';

$types = auth()->user()->admin
$types = auth()->user()->admin || auth()->user()->manager
? Category::pluck('type')
: User::findOrFail(Auth::id())->categories()->pluck('type');

Expand Down
53 changes: 53 additions & 0 deletions app/Mail/UserSubroleChanged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class UserSubroleChanged extends Mailable
{
use Queueable, SerializesModels;

/**
* Create a new message instance.
*/
public function __construct(public User $user)
{
}

/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
to: User::activeAdminsEmails(),
subject: __('emails.user_subrole_changed_subject', ['name' => $this->user->name]),
);
}

/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.user_subrole_changed',
);
}

/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
54 changes: 54 additions & 0 deletions app/Mail/YourSubroleChanged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class YourSubroleChanged extends Mailable
{
use Queueable, SerializesModels;

/**
* Create a new message instance.
*/
public function __construct(public User $user)
{
}

/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
to: [new Address($this->user->email, $this->user->name)],
subject: __('emails.your_subrole_changed_subject'),
);
}

/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.your_subrole_changed',
);
}

/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
1 change: 1 addition & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class User extends Authenticatable
*/
protected $casts = [
'admin' => 'boolean',
'manager' => 'boolean',
'active' => 'boolean',
'login_at' => 'datetime',
];
Expand Down
4 changes: 2 additions & 2 deletions app/Policies/DevicePolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ class DevicePolicy
*/
public function viewAny(User $user): bool
{
return $user->admin || $user->categories()->pluck('type')->count();
return $user->admin || $user->manager || $user->categories()->pluck('type')->count();
}

/**
* Determine whether the user can view the model.
*/
public function view(User $user, Device $device): bool
{
return $user->admin || $user->categories()->pluck('type')->contains($device->category->type);
return $user->admin || $user->manager || $user->categories()->pluck('type')->contains($device->category->type);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('manager')->default(false)->after('admin');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('manager');
});
}
};
7 changes: 7 additions & 0 deletions lang/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@
'login' => 'Login',
'logout' => 'Logout',
'mac' => 'MAC address',
'manager_grant_rights_body' => 'Would you like to grant manager rights to :name?',
'manager_grant_rights' => 'Grant manager rights?',
'manager_grant' => 'Grant manager',
'manager_revoke_rights_body' => 'Would you like to revoke manager rights from :name?',
'manager_revoke_rights' => 'Revoke manager rights?',
'manager_revoke' => 'Revoke manager',
'manager' => 'Manager',
'managers' => 'Managers',
'my_profile' => 'My Profile',
'name' => 'Name',
Expand Down
9 changes: 9 additions & 0 deletions lang/en/emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
'user_role_changed_header' => 'User Role Changed',
'user_role_changed_subject' => ":name's Role Changed",

'user_subrole_changed_body_granted' => ':name has been granted manager rights successfully.',
'user_subrole_changed_body_revoked' => ':name has been revoked manager rights successfully.',
'user_subrole_changed_header' => 'User Role Changed',
'user_subrole_changed_subject' => ":name's Role Changed",

'user_status_changed_body_active' => ':name account has been activated successfully.',
'user_status_changed_body_inactive' => ':name account has been deactivated successfully.',
'user_status_changed_header' => 'User Status Changed',
Expand All @@ -23,6 +28,10 @@
'your_role_changed_body_revoked' => 'You have been revoked administrator rights successfully.',
'your_role_changed_subject' => 'Your Role Changed',

'your_subrole_changed_body_granted' => 'You have been granted manager rights successfully.',
'your_subrole_changed_body_revoked' => 'You have been revoked manager rights successfully.',
'your_subrole_changed_subject' => 'Your Role Changed',

'your_status_changed_body_active' => 'Your account has been activated successfully.',
'your_status_changed_body_inactive' => 'Your account has been deactivated successfully.',
'your_status_changed_subject' => 'Your Status Changed',
Expand Down
2 changes: 2 additions & 0 deletions lang/en/users.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
'cannot_toggle_your_role' => 'You cannot toggle your own role.',
'cannot_tweak_your_roles' => 'You cannot tweak your own roles.',
'deadmined' => ':name has been successfully removed administrator rights.',
'demanagered' => ':name has been successfully removed manager rights.',
'inactive' => ':name has been successfully deactivated.',
'managered' => ':name has been successfully granted manager rights.',
'none_found' => 'No user found.',
'profile' => "Users's profile",
'roles_updated' => 'Roles updated.',
Expand Down
7 changes: 7 additions & 0 deletions resources/views/components/user-status.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ class="dark:bg-indigo-800 dark:text-indigo-100 px-2 ml-2 text-xs font-semibold t
{{ __('common.administrator') }}
</span>
@endif

@if ($model->manager)
<span
class="dark:bg-indigo-800 dark:text-indigo-100 px-2 ml-2 text-xs font-semibold text-indigo-800 bg-indigo-100 rounded-full">
{{ __('common.manager') }}
</span>
@endif
6 changes: 4 additions & 2 deletions resources/views/devices/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

@section('subheader')

<a class="hover:bg-gray-200 dark:bg-gray-900 dark:hover:bg-gray-700 px-2 py-1 text-sm bg-gray-300 border border-gray-400 rounded"
href="{{ route('devices.create') }}">{{ __('common.add') }}</a>
@can('create', App\Models\Device::class)
<a class="hover:bg-gray-200 dark:bg-gray-900 dark:hover:bg-gray-700 px-2 py-1 text-sm bg-gray-300 border border-gray-400 rounded"
href="{{ route('devices.create') }}">{{ __('common.add') }}</a>
@endcan

@endsection

Expand Down
8 changes: 8 additions & 0 deletions resources/views/emails/user_subrole_changed.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<x-mail::message>

# {{ __('emails.user_subrole_changed_header') }}

{{ $user->manager ? __('emails.user_subrole_changed_body_granted', ['name' => $user->name]) : __('emails.user_subrole_changed_body_revoked', ['name' => $user->name]) }}

{{ config('app.name') }}
</x-mail::message>
8 changes: 8 additions & 0 deletions resources/views/emails/your_subrole_changed.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<x-mail::message>

# {{ __('emails.your_subrole_changed_subject') }}

{{ $user->manager ? __('emails.your_subrole_changed_body_granted') : __('emails.your_subrole_changed_body_revoked') }}

{{ config('app.name') }}
</x-mail::message>
25 changes: 25 additions & 0 deletions resources/views/users/partials/subrole.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<form x-data="{ open: false }" class="inline-block" action="{{ route('users.subrole', $user) }}" method="POST">
@csrf
@method('patch')

@if ($user->manager)
<x-button @click.prevent="open = !open" color="blue">{{ __('common.manager_revoke') }}</x-button>
@else
<x-button @click.prevent="open = !open" color="red">{{ __('common.manager_grant') }}</x-button>
@endif

<x-modal>
<x-slot:title>
@if ($user->manager)
{{ __('common.manager_revoke_rights') }}
@else
{{ __('common.manager_grant_rights') }}
@endif
</x-slot:title>
@if ($user->manager)
{{ __('common.manager_revoke_rights_body', ['name' => $user->name]) }}
@else
{{ __('common.manager_grant_rights_body', ['name' => $user->name]) }}
@endif
</x-modal>
</form>
6 changes: 5 additions & 1 deletion resources/views/users/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
</x-dl-div>

@can('do-everything')
@unless($user->id == auth()->id())
@unless ($user->id == auth()->id())
<x-dl-div>
<x-slot:dt>{{ __('common.roles') }}</x-slot:dt>
<x-user-roles :user="$user" :categories="$categories" />
Expand All @@ -68,6 +68,10 @@
!request()->user()->is($user),
'users.partials.status')

@includeWhen(request()->user()->can('do-everything') &&
!request()->user()->is($user),
'users.partials.subrole')

@includeWhen(request()->user()->can('do-everything') &&
!request()->user()->is($user),
'users.partials.role')
Expand Down
2 changes: 2 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Http\Controllers\UserController;
use App\Http\Controllers\UserRoleController;
use App\Http\Controllers\UserStatusController;
use App\Http\Controllers\UserSubroleController;
use Illuminate\Support\Facades\Route;

/*
Expand Down Expand Up @@ -37,6 +38,7 @@

Route::patch('users/{user}/categories', [UserCategoryController::class, 'update'])->name('users.categories');
Route::patch('users/{user}/role', [UserRoleController::class, 'update'])->name('users.role');
Route::patch('users/{user}/subrole', [UserSubroleController::class, 'update'])->name('users.subrole');
Route::patch('users/{user}/status', [UserStatusController::class, 'update'])->name('users.status');

Route::get('login', [ShibbolethController::class, 'create'])->name('login')->middleware('guest');
Expand Down
Loading

0 comments on commit a4d4d18

Please sign in to comment.