From 4d22a782913daaf5cb159237383ee113dd0dc864 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Fri, 25 Jul 2025 20:06:20 +0200 Subject: [PATCH 01/14] Membership list in profile --- app/Livewire/Profile/Memberships.php | 38 +++++++++++ lang/de/profile.php | 13 ++++ lang/en/profile.php | 13 ++++ resources/views/livewire/profile.blade.php | 8 ++- .../livewire/profile/memberships.blade.php | 64 +++++++++++++++++++ routes/breadcrumbs.php | 5 ++ routes/web.php | 1 + 7 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 app/Livewire/Profile/Memberships.php create mode 100644 lang/de/profile.php create mode 100644 lang/en/profile.php create mode 100644 resources/views/livewire/profile/memberships.blade.php diff --git a/app/Livewire/Profile/Memberships.php b/app/Livewire/Profile/Memberships.php new file mode 100644 index 00000000..4ba6fd4d --- /dev/null +++ b/app/Livewire/Profile/Memberships.php @@ -0,0 +1,38 @@ +user()->username); + if ($this->showOnlyActive) { + $query->whereNull('until'); + } + $roleMemberships = $query->get(); + $memberships = []; + foreach ($roleMemberships as $row) { + $role = Role::findOrFail('cn=' . $row->role_cn . ',' . $row->committee_dn); + array_push($memberships, [ + 'role' => $role, + 'from' => $row->from, + 'until' => $row->until, + 'decided' => $row->decided, + 'comment' => $row->comment, + ]); + } + + return view('livewire.profile.memberships', [ + 'memberships' => $memberships, + ])->title(__('Profile')); + } +} diff --git a/lang/de/profile.php b/lang/de/profile.php new file mode 100644 index 00000000..cc8c0d35 --- /dev/null +++ b/lang/de/profile.php @@ -0,0 +1,13 @@ + 'Tätigkeit', + 'comment' => 'Bemerkung', + 'decision' => 'Beschluss', + 'from' => 'von', + 'in' => 'in', + 'memberships' => 'Mitgliedschaften', + 'showOnlyActiveMemberships' => 'Zeige nur aktive Mitgliedschaften', + 'today' => 'heute', + 'until' => 'bis', +]; \ No newline at end of file diff --git a/lang/en/profile.php b/lang/en/profile.php new file mode 100644 index 00000000..af37e809 --- /dev/null +++ b/lang/en/profile.php @@ -0,0 +1,13 @@ + 'Activity', + 'comment' => 'Comment', + 'decision' => 'Decision', + 'from' => 'from', + 'in' => 'in', + 'memberships' => 'Memberships', + 'showOnlyActiveMemberships' => 'Show only active memberships', + 'today' => 'today', + 'until' => 'until', +]; \ No newline at end of file diff --git a/resources/views/livewire/profile.blade.php b/resources/views/livewire/profile.blade.php index be2dc6c4..e721ee70 100644 --- a/resources/views/livewire/profile.blade.php +++ b/resources/views/livewire/profile.blade.php @@ -1,9 +1,11 @@
-
-
-

{{ __('Profile') }}

+ +
diff --git a/resources/views/livewire/profile/memberships.blade.php b/resources/views/livewire/profile/memberships.blade.php new file mode 100644 index 00000000..c0b69d8b --- /dev/null +++ b/resources/views/livewire/profile/memberships.blade.php @@ -0,0 +1,64 @@ +
+ + +
+
+ +
+ + + + {{ __('profile.activity') }} + + + {{ __('profile.from') }} + + + {{ __('profile.until') }} + + + {{ __('profile.decision') }} + + + {{ __('profile.comment') }} + + + @forelse($memberships as $row) + + + {{ $row['role']->getFirstAttribute('description') }} {{ __('profile.in') }} {{ $row['role']->committee()->getFirstAttribute('description') }} + + + {{ \Carbon\Carbon::parse($row['from'])->format('Y-m-d') }} + + + @if ($row['until'] != '') + {{ \Carbon\Carbon::parse($row['until'])->format('Y-m-d') }} + @else + {{ __('profile.today') }} + @endif + + + {{ \Carbon\Carbon::parse($row['decided'])->format('Y-m-d') }} + + + {{ $row['comment'] }} + + + @empty + + +
+ {{ __('groups.no_roles_found') }} +
+
+
+ @endforelse +
+
+
diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index f46a6671..1a7b938a 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -32,6 +32,11 @@ $trail->push(__('Profile'), route('profile', $routeParams)); }); +Breadcrumbs::for('profile.memberships', function (BreadcrumbTrail $trail, array $routeParams) { + $trail->parent('profile', $routeParams); + $trail->push(__('profile.memberships'), route('profile.memberships', $routeParams)); +}); + Breadcrumbs::for('password.change', function (BreadcrumbTrail $trail, array $routeParams) { $trail->parent('profile', $routeParams); $trail->push(__('Change Password'), route('password.change', $routeParams)); diff --git a/routes/web.php b/routes/web.php index 8549a0bf..7ecfb1ba 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,6 +22,7 @@ return redirect()->route('realms.pick'); }); Route::get('/profile', \App\Livewire\Profile::class)->name('profile'); + Route::get('/profile/memberships', \App\Livewire\Profile\Memberships::class)->name('profile.memberships'); Route::get('/pick-realm', \App\Livewire\Realm\ListRealms::class)->name('realms.pick'); Route::middleware(['communityMember'])->group(function (){ From 19c8c1ba1b2cb79bdbe6d5d91de47e532a606a9e Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Sun, 27 Jul 2025 21:32:43 +0200 Subject: [PATCH 02/14] Fix typo in id --- resources/views/livewire/profile/memberships.blade.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/views/livewire/profile/memberships.blade.php b/resources/views/livewire/profile/memberships.blade.php index c0b69d8b..fb0a1994 100644 --- a/resources/views/livewire/profile/memberships.blade.php +++ b/resources/views/livewire/profile/memberships.blade.php @@ -8,7 +8,8 @@
- + +
From 58a7dbccd54773d91f53956be04a8e747a25d037 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Sun, 27 Jul 2025 23:39:37 +0200 Subject: [PATCH 03/14] Export a users memberships as PDF file --- app/Livewire/Profile/Memberships.php | 29 ++++- composer.json | 3 +- lang/de/profile.php | 3 + lang/en/profile.php | 3 + .../livewire/profile/memberships.blade.php | 9 +- resources/views/pdfs/memberships.blade.php | 122 ++++++++++++++++++ 6 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 resources/views/pdfs/memberships.blade.php diff --git a/app/Livewire/Profile/Memberships.php b/app/Livewire/Profile/Memberships.php index 4ba6fd4d..99baf353 100644 --- a/app/Livewire/Profile/Memberships.php +++ b/app/Livewire/Profile/Memberships.php @@ -4,18 +4,18 @@ use App\Ldap\User; use App\Ldap\Role; -use Livewire\Component; - use App\Models\RoleMembership; +use Barryvdh\DomPDF\Facade\Pdf; +use Livewire\Component; class Memberships extends Component { - public $showOnlyActive = true; + public bool $showOnlyActive = true; - public function render() + public function getMemberships(bool $onlyActive) { $query = RoleMembership::where('username', auth()->user()->username); - if ($this->showOnlyActive) { + if ($onlyActive) { $query->whereNull('until'); } $roleMemberships = $query->get(); @@ -30,9 +30,28 @@ public function render() 'comment' => $row->comment, ]); } + return $memberships; + } + + public function render() + { + $memberships = $this->getMemberships($this->showOnlyActive); return view('livewire.profile.memberships', [ 'memberships' => $memberships, ])->title(__('Profile')); } + + public function exportPdf() + { + $memberships = $this->getMemberships(false); + $pdf = Pdf::loadView('pdfs.memberships', [ + 'fullName' => auth()->user()->full_name, + 'memberships' => $memberships, + ]); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf->stream(); + }, 'memberships-' . auth()->user()->username . '.pdf');; + } } diff --git a/composer.json b/composer.json index 563e247c..0cd62d29 100644 --- a/composer.json +++ b/composer.json @@ -9,11 +9,12 @@ "php": "^8.2", "ext-ldap": "*", "ext-pdo": "*", + "barryvdh/laravel-dompdf": "^3.1", "codegreencreative/laravel-samlidp": "^5.2", "dacoto/laravel-domain-validation": "^3.0", "diglactic/laravel-breadcrumbs": "^8.1", - "directorytree/ldaprecord-laravel": "^3.0", "directorytree/ldaprecord": "v3.5.1", + "directorytree/ldaprecord-laravel": "^3.0", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.0", "laravel/passport": "^11.10", diff --git a/lang/de/profile.php b/lang/de/profile.php index cc8c0d35..2fd141e9 100644 --- a/lang/de/profile.php +++ b/lang/de/profile.php @@ -3,10 +3,13 @@ return [ 'activity' => 'Tätigkeit', 'comment' => 'Bemerkung', + 'committee' => 'Gremium', 'decision' => 'Beschluss', + 'exportAsPdf' => 'Als PDF exportieren', 'from' => 'von', 'in' => 'in', 'memberships' => 'Mitgliedschaften', + 'role' => 'Rolle', 'showOnlyActiveMemberships' => 'Zeige nur aktive Mitgliedschaften', 'today' => 'heute', 'until' => 'bis', diff --git a/lang/en/profile.php b/lang/en/profile.php index af37e809..189a248b 100644 --- a/lang/en/profile.php +++ b/lang/en/profile.php @@ -3,10 +3,13 @@ return [ 'activity' => 'Activity', 'comment' => 'Comment', + 'committee' => 'Committee', 'decision' => 'Decision', + 'exportAsPdf' => 'Export as PDF', 'from' => 'from', 'in' => 'in', 'memberships' => 'Memberships', + 'role' => 'Role', 'showOnlyActiveMemberships' => 'Show only active memberships', 'today' => 'today', 'until' => 'until', diff --git a/resources/views/livewire/profile/memberships.blade.php b/resources/views/livewire/profile/memberships.blade.php index fb0a1994..bf5a43e4 100644 --- a/resources/views/livewire/profile/memberships.blade.php +++ b/resources/views/livewire/profile/memberships.blade.php @@ -1,12 +1,17 @@
+
+
+ {{ __('profile.exportAsPdf') }} +
+
diff --git a/resources/views/pdfs/memberships.blade.php b/resources/views/pdfs/memberships.blade.php new file mode 100644 index 00000000..62849ffa --- /dev/null +++ b/resources/views/pdfs/memberships.blade.php @@ -0,0 +1,122 @@ + + + + + + + + +

{{ __('profile.memberships') }}

+

{{ $fullName }} | {{ date("Y-m-d") }}

+ + + + + + + + + + + + @foreach ($memberships as $row) + + + + + + + @endforeach + +
{{ __('profile.role') }}{{ __('profile.committee') }}{{ __('profile.from') }}{{ __('profile.until') }}
{{ $row['role']->getFirstAttribute('description') }}{{ $row['role']->committee()->getFirstAttribute('description') }}{{ \Carbon\Carbon::parse($row['from'])->format('Y-m-d') }} + @if ($row['until'] != '') + {{ \Carbon\Carbon::parse($row['until'])->format('Y-m-d') }} + @else + {{ __('profile.today') }} + @endif +
+ + \ No newline at end of file From 0e7b81e35610f052ca61ed201adcc6c5fff577d2 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Mon, 28 Jul 2025 19:33:07 +0200 Subject: [PATCH 04/14] Generate membership PDF in member list --- app/Livewire/Profile/Memberships.php | 24 ++++++++++++++++++------ app/Livewire/Realm/ListMembers.php | 17 +++++++++++++++++ lang/de/profile.php | 2 ++ lang/en/profile.php | 2 ++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/app/Livewire/Profile/Memberships.php b/app/Livewire/Profile/Memberships.php index 99baf353..bb3cad07 100644 --- a/app/Livewire/Profile/Memberships.php +++ b/app/Livewire/Profile/Memberships.php @@ -10,11 +10,21 @@ class Memberships extends Component { + public $currentUsername; public bool $showOnlyActive = true; - public function getMemberships(bool $onlyActive) + public function mount($username) { - $query = RoleMembership::where('username', auth()->user()->username); + if ($username != "") { + $this->currentUsername = $username; + } else { + $this->currentUsername = auth()->user()->username; + } + } + + public function getMemberships(string $username, bool $onlyActive) + { + $query = RoleMembership::where('username', $username); if ($onlyActive) { $query->whereNull('until'); } @@ -35,7 +45,7 @@ public function getMemberships(bool $onlyActive) public function render() { - $memberships = $this->getMemberships($this->showOnlyActive); + $memberships = $this->getMemberships($this->currentUsername, $this->showOnlyActive); return view('livewire.profile.memberships', [ 'memberships' => $memberships, @@ -44,14 +54,16 @@ public function render() public function exportPdf() { - $memberships = $this->getMemberships(false); + $memberships = $this->getMemberships($this->currentUsername, false); + $user = User::findOrFailByUsername($this->currentUsername); $pdf = Pdf::loadView('pdfs.memberships', [ - 'fullName' => auth()->user()->full_name, + 'fullName' => $user->cn[0], + 'community' => null, 'memberships' => $memberships, ]); return response()->streamDownload(function () use ($pdf) { echo $pdf->stream(); - }, 'memberships-' . auth()->user()->username . '.pdf');; + }, 'memberships-' . $this->currentUsername . '.pdf');; } } diff --git a/app/Livewire/Realm/ListMembers.php b/app/Livewire/Realm/ListMembers.php index 754a9d8b..9d1dd6dc 100644 --- a/app/Livewire/Realm/ListMembers.php +++ b/app/Livewire/Realm/ListMembers.php @@ -4,6 +4,7 @@ use App\Ldap\Community; use App\Ldap\User; +use Barryvdh\DomPDF\Facade\Pdf; use Livewire\Attributes\Rule; use Livewire\Attributes\Url; use Livewire\Component; @@ -87,4 +88,20 @@ public function close(): void $this->showDeleteModal = false; unset($this->deleteMemberName, $this->deleteMemberUsername); } + + public function exportPdf($username) + { + $memberships = app('App\Livewire\Profile\Memberships')->getMemberships($username, false); + $user = User::findOrFailByUsername($username); + $community = Community::findOrFailByUid($this->community_name); + $pdf = Pdf::loadView('pdfs.memberships', [ + 'fullName' => $user->cn[0], + 'community' => $community->description[0], + 'memberships' => $memberships, + ]); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf->stream(); + }, 'memberships-' . $username . '.pdf');; + } } diff --git a/lang/de/profile.php b/lang/de/profile.php index 2fd141e9..36af4639 100644 --- a/lang/de/profile.php +++ b/lang/de/profile.php @@ -2,6 +2,7 @@ return [ 'activity' => 'Tätigkeit', + 'activities' => 'Tätigkeiten', 'comment' => 'Bemerkung', 'committee' => 'Gremium', 'decision' => 'Beschluss', @@ -9,6 +10,7 @@ 'from' => 'von', 'in' => 'in', 'memberships' => 'Mitgliedschaften', + 'membershipsAsPdf' => 'Mitgliedschaften als PDF', 'role' => 'Rolle', 'showOnlyActiveMemberships' => 'Zeige nur aktive Mitgliedschaften', 'today' => 'heute', diff --git a/lang/en/profile.php b/lang/en/profile.php index 189a248b..914c8f9b 100644 --- a/lang/en/profile.php +++ b/lang/en/profile.php @@ -2,6 +2,7 @@ return [ 'activity' => 'Activity', + 'activities' => 'Activities', 'comment' => 'Comment', 'committee' => 'Committee', 'decision' => 'Decision', @@ -9,6 +10,7 @@ 'from' => 'from', 'in' => 'in', 'memberships' => 'Memberships', + 'membershipsAsPdf' => 'Memberships as PDF', 'role' => 'Role', 'showOnlyActiveMemberships' => 'Show only active memberships', 'today' => 'today', From b5b23effab687c851ea4f0516389bd75b17aab09 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Mon, 28 Jul 2025 19:34:16 +0200 Subject: [PATCH 05/14] Use username in Profile URL and move password change to profile --- app/Livewire/ChangePassword.php | 11 +++++++++++ resources/views/components/navigation.blade.php | 7 ++----- resources/views/livewire/change-password.blade.php | 9 ++++++--- resources/views/livewire/profile.blade.php | 5 +++-- .../views/livewire/profile/memberships.blade.php | 5 +++-- .../livewire/realm/community-dashboard.blade.php | 2 +- routes/auth.php | 2 +- routes/breadcrumbs.php | 2 +- routes/web.php | 4 ++-- 9 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/Livewire/ChangePassword.php b/app/Livewire/ChangePassword.php index 684a6575..15211218 100644 --- a/app/Livewire/ChangePassword.php +++ b/app/Livewire/ChangePassword.php @@ -18,6 +18,17 @@ class ChangePassword extends Component public string $password_confirmation; + public $currentUsername; + + public function mount($username) + { + if ($username != "") { + $this->currentUsername = $username; + } else { + $this->currentUsername = auth()->user()->username; + } + } + public function rules(): array { return [ diff --git a/resources/views/components/navigation.blade.php b/resources/views/components/navigation.blade.php index c16c72ff..b8fe48a9 100644 --- a/resources/views/components/navigation.blade.php +++ b/resources/views/components/navigation.blade.php @@ -64,10 +64,7 @@ - {{ __('Profile') }} - {{ __('Change Password') }} - -
+ {{ __('Profile') }}
@@ -141,7 +138,7 @@
{{ Auth::user()->email }}
+ :href="route('profile', ['username' => auth()->user()->username])"> {{ __('Profil') }}
diff --git a/resources/views/livewire/change-password.blade.php b/resources/views/livewire/change-password.blade.php index 25fd4475..312c6ee4 100644 --- a/resources/views/livewire/change-password.blade.php +++ b/resources/views/livewire/change-password.blade.php @@ -1,9 +1,12 @@ -
-
-

{{ __('Change Password') }}

+ + {{ __('user.help.password') }} diff --git a/resources/views/livewire/profile.blade.php b/resources/views/livewire/profile.blade.php index e721ee70..5e879283 100644 --- a/resources/views/livewire/profile.blade.php +++ b/resources/views/livewire/profile.blade.php @@ -1,8 +1,9 @@
diff --git a/resources/views/livewire/profile/memberships.blade.php b/resources/views/livewire/profile/memberships.blade.php index bf5a43e4..8373996d 100644 --- a/resources/views/livewire/profile/memberships.blade.php +++ b/resources/views/livewire/profile/memberships.blade.php @@ -1,8 +1,9 @@
diff --git a/resources/views/livewire/realm/community-dashboard.blade.php b/resources/views/livewire/realm/community-dashboard.blade.php index 5a8e532f..108b696c 100644 --- a/resources/views/livewire/realm/community-dashboard.blade.php +++ b/resources/views/livewire/realm/community-dashboard.blade.php @@ -4,7 +4,7 @@

{{ __('realms.dashboard.explanation', ['name' => $name]) }}

- + {{ __('realms.dashboard.profile_heading', ['name' => $name]) }} diff --git a/routes/auth.php b/routes/auth.php index 14e8251a..85516c46 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -49,7 +49,7 @@ Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); - Route::any('change-password', ChangePassword::class) + Route::any('profile/{username}/password', ChangePassword::class) ->name('password.change'); Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 1a7b938a..0163e41e 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -29,7 +29,7 @@ }); Breadcrumbs::for('profile', function (BreadcrumbTrail $trail, array $routeParams) { - $trail->push(__('Profile'), route('profile', $routeParams)); + $trail->push(__('Profile'), route('profile', array_merge(['username' => auth()->user()->username], $routeParams))); }); Breadcrumbs::for('profile.memberships', function (BreadcrumbTrail $trail, array $routeParams) { diff --git a/routes/web.php b/routes/web.php index 7ecfb1ba..85cf4190 100644 --- a/routes/web.php +++ b/routes/web.php @@ -21,8 +21,8 @@ Route::get('/', static function (){ return redirect()->route('realms.pick'); }); - Route::get('/profile', \App\Livewire\Profile::class)->name('profile'); - Route::get('/profile/memberships', \App\Livewire\Profile\Memberships::class)->name('profile.memberships'); + Route::get('/profile/{username}', \App\Livewire\Profile::class)->name('profile'); + Route::get('/profile/{username}/memberships', \App\Livewire\Profile\Memberships::class)->name('profile.memberships'); Route::get('/pick-realm', \App\Livewire\Realm\ListRealms::class)->name('realms.pick'); Route::middleware(['communityMember'])->group(function (){ From 8d57d4ebe2b0f6b0b57a7be28d5cc3809fb354b3 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Mon, 28 Jul 2025 19:35:17 +0200 Subject: [PATCH 06/14] Membership PDF and generate button on member index --- resources/views/livewire/realm/members.blade.php | 13 ++++++++----- resources/views/pdfs/memberships.blade.php | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/resources/views/livewire/realm/members.blade.php b/resources/views/livewire/realm/members.blade.php index 07adddae..073161e9 100644 --- a/resources/views/livewire/realm/members.blade.php +++ b/resources/views/livewire/realm/members.blade.php @@ -35,11 +35,14 @@ {{ $realm_member->cn[0] }} {{ $realm_member->uid[0] }} - {{ __('Remove Member') }} - +
+ {{ __('profile.membershipsAsPdf') }} + {{ __('Remove Member') }} + +
@empty diff --git a/resources/views/pdfs/memberships.blade.php b/resources/views/pdfs/memberships.blade.php index 62849ffa..d6b39c80 100644 --- a/resources/views/pdfs/memberships.blade.php +++ b/resources/views/pdfs/memberships.blade.php @@ -89,8 +89,8 @@ -

{{ __('profile.memberships') }}

-

{{ $fullName }} | {{ date("Y-m-d") }}

+

{{ __('profile.activities') }}

+

{{ $fullName }} @if($community)| {{ $community }} @endif| {{ date("Y-m-d") }}

From ffca06644ae37a2a031b8f846e8218b8c64e3a3c Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Tue, 29 Jul 2025 00:13:55 +0200 Subject: [PATCH 07/14] Only allow the user itself and superadmins to edit user data --- app/Livewire/ChangePassword.php | 8 +++++--- app/Livewire/Profile.php | 17 +++++++++++------ app/Livewire/Profile/Memberships.php | 6 ++++-- .../views/livewire/realm/members.blade.php | 8 +++++++- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/app/Livewire/ChangePassword.php b/app/Livewire/ChangePassword.php index 15211218..f49f7c01 100644 --- a/app/Livewire/ChangePassword.php +++ b/app/Livewire/ChangePassword.php @@ -22,11 +22,13 @@ class ChangePassword extends Component public function mount($username) { - if ($username != "") { + if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) { $this->currentUsername = $username; - } else { + } elseif ($username == auth()->user()->username) { $this->currentUsername = auth()->user()->username; - } + } else { + abort('403'); + } } public function rules(): array diff --git a/app/Livewire/Profile.php b/app/Livewire/Profile.php index adf2fd67..bb826aa5 100644 --- a/app/Livewire/Profile.php +++ b/app/Livewire/Profile.php @@ -19,10 +19,18 @@ class Profile extends Component #[Rule('string|required')] public string $fullName; - public function mount() + public $currentUsername; + + public function mount($username) { - $username = Auth::user()->username; - $user = User::findOrFailByUsername($username); + if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) { + $this->currentUsername = $username; + } elseif ($username == auth()->user()->username) { + $this->currentUsername = auth()->user()->username; + } else { + abort('403'); + } + $user = User::findOrFailByUsername($this->currentUsername); $this->uid = $user->getFirstAttribute('uid'); $this->fullName = $user->getFirstAttribute('cn'); $this->email = $user->getFirstAttribute('mail'); @@ -36,9 +44,6 @@ public function render() public function save() { $this->validate(); - if (Auth::user()->username !== $this->uid) { - abort('500'); - } $user = User::findOrFailByUsername($this->uid); $user->setAttribute('mail', $this->email); $user->setAttribute('cn', $this->fullName); diff --git a/app/Livewire/Profile/Memberships.php b/app/Livewire/Profile/Memberships.php index bb3cad07..85cc7f7c 100644 --- a/app/Livewire/Profile/Memberships.php +++ b/app/Livewire/Profile/Memberships.php @@ -15,10 +15,12 @@ class Memberships extends Component public function mount($username) { - if ($username != "") { + if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) { $this->currentUsername = $username; - } else { + } elseif ($username == auth()->user()->username) { $this->currentUsername = auth()->user()->username; + } else { + abort('403'); } } diff --git a/resources/views/livewire/realm/members.blade.php b/resources/views/livewire/realm/members.blade.php index 073161e9..ce241bfa 100644 --- a/resources/views/livewire/realm/members.blade.php +++ b/resources/views/livewire/realm/members.blade.php @@ -36,7 +36,13 @@ {{ $realm_member->uid[0] }}
- {{ __('profile.membershipsAsPdf') }} + + {{ __('profile.membershipsAsPdf') }} + Date: Mon, 28 Jul 2025 22:48:03 +0200 Subject: [PATCH 08/14] Display role and committee seperately --- resources/views/livewire/profile/memberships.blade.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/profile/memberships.blade.php b/resources/views/livewire/profile/memberships.blade.php index 8373996d..c5a3db13 100644 --- a/resources/views/livewire/profile/memberships.blade.php +++ b/resources/views/livewire/profile/memberships.blade.php @@ -20,7 +20,10 @@ - {{ __('profile.activity') }} + {{ __('profile.role') }} + + + {{ __('profile.committee') }} {{ __('profile.from') }} @@ -38,7 +41,10 @@ @forelse($memberships as $row) - {{ $row['role']->getFirstAttribute('description') }} {{ __('profile.in') }} {{ $row['role']->committee()->getFirstAttribute('description') }} + {{ $row['role']->getFirstAttribute('description') }} + + + {{ $row['role']->committee()->getFirstAttribute('description') }} {{ \Carbon\Carbon::parse($row['from'])->format('Y-m-d') }} From 7704b76ff3e03cf578eadd57d771fb501c27168f Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Mon, 28 Jul 2025 22:54:41 +0200 Subject: [PATCH 09/14] Link to user profile on members list for superadmins --- resources/views/livewire/realm/members.blade.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/views/livewire/realm/members.blade.php b/resources/views/livewire/realm/members.blade.php index ce241bfa..89a7ceae 100644 --- a/resources/views/livewire/realm/members.blade.php +++ b/resources/views/livewire/realm/members.blade.php @@ -32,7 +32,15 @@ @forelse($realm_members as $realm_member) - {{ $realm_member->cn[0] }} + + @if (auth()->user()->can('superadmin', \App\Ldap\User::class)) + + {{ $realm_member->cn[0] }} + + @else + {{ $realm_member->cn[0] }} + @endif + {{ $realm_member->uid[0] }}
From 30dea9c07e39d64c6e735b65666de72ad9abda8c Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Mon, 28 Jul 2025 22:56:57 +0200 Subject: [PATCH 10/14] Remove r --- resources/views/livewire/profile/memberships.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/profile/memberships.blade.php b/resources/views/livewire/profile/memberships.blade.php index c5a3db13..1e3d0060 100644 --- a/resources/views/livewire/profile/memberships.blade.php +++ b/resources/views/livewire/profile/memberships.blade.php @@ -3,7 +3,7 @@
From 0223da5f54e3bb29588ce0cbac93343f49760926 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Wed, 26 Nov 2025 02:53:36 +0100 Subject: [PATCH 11/14] Use Locked attribute and three instead of two = --- app/Livewire/ChangePassword.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Livewire/ChangePassword.php b/app/Livewire/ChangePassword.php index f49f7c01..73744f8c 100644 --- a/app/Livewire/ChangePassword.php +++ b/app/Livewire/ChangePassword.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rules\Password; +use Livewire\Attributes\Locked; use Livewire\Attributes\Rule; use Livewire\Component; use Mockery\Generator\StringManipulation\Pass\Pass; @@ -18,13 +19,14 @@ class ChangePassword extends Component public string $password_confirmation; + #[Locked] public $currentUsername; public function mount($username) { - if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) { + if ($username === auth()->user()->username || auth()->user()->can('superadmin', User::class)) { $this->currentUsername = $username; - } elseif ($username == auth()->user()->username) { + } elseif ($username === auth()->user()->username) { $this->currentUsername = auth()->user()->username; } else { abort('403'); From 664ef0055c92ec314bdf1e139db2f546f7a2cb4e Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Wed, 26 Nov 2025 02:54:12 +0100 Subject: [PATCH 12/14] Replace array_push and use Locked attribute --- app/Livewire/Profile/Memberships.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Profile/Memberships.php b/app/Livewire/Profile/Memberships.php index 85cc7f7c..4cdddbd5 100644 --- a/app/Livewire/Profile/Memberships.php +++ b/app/Livewire/Profile/Memberships.php @@ -6,11 +6,14 @@ use App\Ldap\Role; use App\Models\RoleMembership; use Barryvdh\DomPDF\Facade\Pdf; +use Livewire\Attributes\Locked; use Livewire\Component; class Memberships extends Component { + #[Locked] public $currentUsername; + public bool $showOnlyActive = true; public function mount($username) @@ -34,13 +37,13 @@ public function getMemberships(string $username, bool $onlyActive) $memberships = []; foreach ($roleMemberships as $row) { $role = Role::findOrFail('cn=' . $row->role_cn . ',' . $row->committee_dn); - array_push($memberships, [ + $memberships[] = [ 'role' => $role, 'from' => $row->from, 'until' => $row->until, 'decided' => $row->decided, 'comment' => $row->comment, - ]); + ]; } return $memberships; } @@ -66,6 +69,6 @@ public function exportPdf() return response()->streamDownload(function () use ($pdf) { echo $pdf->stream(); - }, 'memberships-' . $this->currentUsername . '.pdf');; + }, strtolower(trans('profile.memberships')) . '_' . $this->currentUsername . '.pdf'); } } From a0ef3b9099d00152d0d2ce77e530c5ad4934650c Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Wed, 26 Nov 2025 02:54:26 +0100 Subject: [PATCH 13/14] Use getFirstAttribute --- resources/views/livewire/realm/members.blade.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/views/livewire/realm/members.blade.php b/resources/views/livewire/realm/members.blade.php index 89a7ceae..9ab717b5 100644 --- a/resources/views/livewire/realm/members.blade.php +++ b/resources/views/livewire/realm/members.blade.php @@ -34,18 +34,18 @@ @if (auth()->user()->can('superadmin', \App\Ldap\User::class)) - - {{ $realm_member->cn[0] }} + + {{ $realm_member->getFirstAttribute('cn') }} @else - {{ $realm_member->cn[0] }} + {{ $realm_member->getFirstAttribute('cn') }} @endif - {{ $realm_member->uid[0] }} + {{ $realm_member->getFirstAttribute('uid') }}
@@ -54,7 +54,7 @@ class="ml-auto" {{ __('Remove Member') }} + wire:click="deletePrepare('{{ $realm_member->getFirstAttribute('uid') }}')">{{ __('Remove Member') }}
From 8277e4c8b698ba97f71bb4f28fada8efde3749e1 Mon Sep 17 00:00:00 2001 From: Marc Schlagenhauf Date: Wed, 26 Nov 2025 03:25:36 +0100 Subject: [PATCH 14/14] Sync script (#47) * Save relation between groups and roles in database and sync group members to LDAP by script * Script to move the roles of all groups from LDAP to the database --- app/Console/Commands/LdapSyncRoles.php | 41 ++++++++++++- .../MoveGroupRolesFromLdapToDatabase.php | 60 +++++++++++++++++++ app/Livewire/Group/AddRoleToGroup.php | 11 +++- app/Livewire/Group/ListRolesInGroup.php | 28 +++------ app/Models/GroupMembership.php | 37 ++++++++++++ ...6_27_095320_create_role_group_relation.php | 29 +++++++++ 6 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 app/Console/Commands/MoveGroupRolesFromLdapToDatabase.php create mode 100644 app/Models/GroupMembership.php create mode 100644 database/migrations/2025_06_27_095320_create_role_group_relation.php diff --git a/app/Console/Commands/LdapSyncRoles.php b/app/Console/Commands/LdapSyncRoles.php index c7e984e0..1898c5f7 100644 --- a/app/Console/Commands/LdapSyncRoles.php +++ b/app/Console/Commands/LdapSyncRoles.php @@ -4,11 +4,14 @@ use App\Ldap\Committee; use App\Ldap\Community; +use App\Ldap\Group; use App\Ldap\Role; +use App\Models\GroupMembership; use App\Models\RoleMembership; use Carbon\Carbon; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Isolatable; +use Illuminate\Support\Facades\DB; class LdapSyncRoles extends Command { @@ -45,6 +48,9 @@ public function handle() ->setDn(Community::$rootDn) ->search('ou', $this->argument('community')) ->get(); + + $this->comment("Committees:"); + foreach ($realms as $realm){ $committees = Committee::fromCommunity($realm->getFirstAttribute('ou')) ->search('ou', $this->argument('committee')) @@ -60,7 +66,7 @@ public function handle() ->where('committee_dn', $committee->getDn()) ->where('role_cn', $role->getFirstAttribute('cn')) ->get(); - $this->comment(" |-> " .$role->getDn()); + $this->comment(" |-> " . $role->getDn()); // delete all members so far $role->setAttribute('uniqueMember', ['']); $ldapMembers = $role->members(); @@ -73,5 +79,38 @@ public function handle() } } } + + $this->comment("\nGroups:"); + + foreach ($realms as $realm) { + $groups = Group::fromCommunity($realm->getFirstAttribute('ou')) + ->search('ou', $this->argument('group')) + ->get(); + + foreach ($groups as $group) { + $this->comment("> " . $group->getDn()); + + // delete all members so far + $group->setAttribute('uniqueMember', ['']); + + $roles = GroupMembership::where('group_dn', $group->getDn())->get(); + + foreach ($roles as $role) { + $roleCn = str_replace('cn=', '', substr($role->role_dn, 0, strpos($role->role_dn, ','))); + $committeeDn = strstr($role->role_dn, "ou="); + $activeMemberships = RoleMembership::active($date) + ->where('committee_dn', $committeeDn) + ->where('role_cn', $roleCn) + ->get(); + + $ldapMembers = $group->users(); + foreach ($activeMemberships as $membership) { + // add only active members back + $this->comment(" |-> $membership->username"); + $ldapMembers->attach($membership->user->ldap()); + } + } + } + } } } diff --git a/app/Console/Commands/MoveGroupRolesFromLdapToDatabase.php b/app/Console/Commands/MoveGroupRolesFromLdapToDatabase.php new file mode 100644 index 00000000..3f1d31bb --- /dev/null +++ b/app/Console/Commands/MoveGroupRolesFromLdapToDatabase.php @@ -0,0 +1,60 @@ +list() // only first level + ->setDn(Community::$rootDn) + ->search('ou', $this->argument('community')) + ->get(); + + foreach ($realms as $realm) { + $groups = Group::fromCommunity($realm->getFirstAttribute('ou')) + ->search('ou', $this->argument('group')) + ->get(); + + foreach ($groups as $group) { + $this->comment("> " . $group->getDn()); + + // get roles + $roles = $group->members()->get(); + + foreach ($roles as $role) { + $this->comment($role); + GroupMembership::create([ + 'group_dn' => $group->getDn(), + 'role_dn' => $role, + ]); + } + } + } + } +} diff --git a/app/Livewire/Group/AddRoleToGroup.php b/app/Livewire/Group/AddRoleToGroup.php index 78e2045b..6974567c 100644 --- a/app/Livewire/Group/AddRoleToGroup.php +++ b/app/Livewire/Group/AddRoleToGroup.php @@ -9,6 +9,8 @@ use Livewire\Attributes\Locked; use Livewire\Component; +use App\Models\GroupMembership; + class AddRoleToGroup extends Component { #[Locked] @@ -42,9 +44,12 @@ public function render() public function save() { - /** @var Group $group */ - $group = Group::findOrFail(Group::dnFrom($this->uid, $this->group_cn)); - $group->members()->attach($this->selected_role_dn); + $group_dn = Group::findOrFail(Group::dnFrom($this->uid, $this->group_cn)); + GroupMembership::create([ + 'group_dn' => $group_dn, + 'role_dn' => $this->selected_role_dn, + ]); + return redirect()->route('realms.groups.roles', ['uid' => $this->uid, 'cn' => $this->group_cn]) ->with('message', __('groups.success_role_add')) ; diff --git a/app/Livewire/Group/ListRolesInGroup.php b/app/Livewire/Group/ListRolesInGroup.php index d21b0dce..a38c127d 100644 --- a/app/Livewire/Group/ListRolesInGroup.php +++ b/app/Livewire/Group/ListRolesInGroup.php @@ -5,6 +5,7 @@ use App\Ldap\Community; use App\Ldap\Group; use App\Ldap\Role; +use App\Models\GroupMembership; use Livewire\Attributes\Url; use Livewire\Component; use Livewire\WithPagination; @@ -51,16 +52,15 @@ public function updatedSearch(): void } public function render() { - /** @var Group $group */ - $group = Group::findOrFail($this->group_dn); - $roles = $group->members()->get(); - $users = $group->users()->get(); - // slice breaks it, whyever - get to go. + $rolesDB = GroupMembership::select('role_dn')->where('group_dn', $this->group_dn)->get(); + $roles = []; + foreach ($rolesDB as $row) { + $role = Role::findOrFail($row->role_dn); + array_push($roles, $role); + } return view( 'livewire.group.roles', [ 'roles' => $roles, - 'users' => $users, - 'group' => $group, ] )->title(__('groups.roles_list_title', ['name' => $this->group_cn])); } @@ -75,14 +75,7 @@ public function deletePrepare(string $role_dn): void $committee = $role->committee(); $this->deleteRoleDN = $role_dn; - $this->deleteRoleName = [ - 'role_short' => $role->getFirstAttribute('description'), - 'role_name' => $role->getFirstAttribute('cn'), - 'committee_name' => $committee?->getFirstAttribute('description'), - 'committee_short' => $committee?->getFirstAttribute('ou'), - 'group_short' => $group->getFirstAttribute('cn'), - 'group_name' => $group->getFirstAttribute('description') - ]; + $this->deleteRoleName = [ $role->getFirstAttribute('cn') ]; $this->showDeleteModal = true; } @@ -92,10 +85,7 @@ public function deleteCommit(): void $community = Community::findByUid($this->realm_uid); $this->authorize('delete', [Group::class, $community]); - $group = Group::findOrFail($this->group_dn); - $role = Role::findOrFail($this->deleteRoleDN); - - $group->roles()->detach($role); + GroupMembership::where('group_dn', $this->group_dn)->where('role_dn', $this->deleteRoleDN)->delete(); $this->close(); } diff --git a/app/Models/GroupMembership.php b/app/Models/GroupMembership.php new file mode 100644 index 00000000..1e0cb05b --- /dev/null +++ b/app/Models/GroupMembership.php @@ -0,0 +1,37 @@ +hasMany(Role::class); + } +} diff --git a/database/migrations/2025_06_27_095320_create_role_group_relation.php b/database/migrations/2025_06_27_095320_create_role_group_relation.php new file mode 100644 index 00000000..781b42dd --- /dev/null +++ b/database/migrations/2025_06_27_095320_create_role_group_relation.php @@ -0,0 +1,29 @@ +id(); + $table->string('group_dn'); + $table->string('role_dn'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('role_group_relation'); + } +};