Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/Livewire/ChangePassword.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ class ChangePassword extends Component

public string $password_confirmation;

public $currentUsername;

public function mount($username)
{
if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=== statt ==

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in Livewire kann man auch $this->authorize() verwenden, dann liefert es aber auch direkt ein Permission denied wenns schief geht. Ich glaube ich fände es schöner eine Policy für PW Changes zu haben und auf die hier zu authorizen. Dann ist wer darf was schön an einem Platz (bei den Policies). Kannst du beim User mit rein nehmen.

$this->currentUsername = $username;
} elseif ($username == auth()->user()->username) {
$this->currentUsername = auth()->user()->username;
} else {
abort('403');
}
}

public function rules(): array
{
return [
Expand Down
17 changes: 11 additions & 6 deletions app/Livewire/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ class Profile extends Component
#[Rule('string|required')]
public string $fullName;

public function mount()
public $currentUsername;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vorsicht! Public probs können via client api frei verändert werden, auch wenn du da kein Input feld hin machst. Macht das hier Probleme? Evtl das [#Url] oder [#Locked] Prop verwenden stattdessen


public function mount($username)
{
$username = Auth::user()->username;
$user = User::findOrFailByUsername($username);
if ($username == auth()->user()->username || auth()->user()->can('superadmin', User::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.o.

$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');
Expand All @@ -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);
Expand Down
71 changes: 71 additions & 0 deletions app/Livewire/Profile/Memberships.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace App\Livewire\Profile;

use App\Ldap\User;
use App\Ldap\Role;
use App\Models\RoleMembership;
use Barryvdh\DomPDF\Facade\Pdf;
use Livewire\Component;

class Memberships extends Component
{
public $currentUsername;
public bool $showOnlyActive = true;

public function mount($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');
}
}

public function getMemberships(string $username, bool $onlyActive)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public Methoden können bei livewire auch vom client aus aufgerufen werden. Wenn du das nicht machst (wie hier?) solltest du die private machen

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moderator:innen einer Studischaft haben keinen Zugriff auf den Profil-Bereich. Damit Moderator:innen trotzdem die Möglichkeit haben, die Tätigkeitsübersicht zu exportieren und es möglichst wenig Code-Dopplung gibt, greift auch der Members-Controller auf diese Funktion zu.

{
$query = RoleMembership::where('username', $username);
if ($onlyActive) {
$query->whereNull('until');
}
$roleMemberships = $query->get();
$memberships = [];
foreach ($roleMemberships as $row) {
$role = Role::findOrFail('cn=' . $row->role_cn . ',' . $row->committee_dn);
array_push($memberships, [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: If you use array_push() to add one element to the array, it's better to use $array[] = because in that way there is no overhead of calling a function.
https://www.php.net/manual/en/function.array-push.php

Ich finde $memberships[] = [...]; auch leichter verständlich was passiert :)

'role' => $role,
'from' => $row->from,
'until' => $row->until,
'decided' => $row->decided,
'comment' => $row->comment,
]);
}
return $memberships;
}

public function render()
{
$memberships = $this->getMemberships($this->currentUsername, $this->showOnlyActive);

return view('livewire.profile.memberships', [
'memberships' => $memberships,
])->title(__('Profile'));
}

public function exportPdf()
{
$memberships = $this->getMemberships($this->currentUsername, false);
$user = User::findOrFailByUsername($this->currentUsername);
$pdf = Pdf::loadView('pdfs.memberships', [
'fullName' => $user->cn[0],
'community' => null,
'memberships' => $memberships,
]);

return response()->streamDownload(function () use ($pdf) {
echo $pdf->stream();
}, 'memberships-' . $this->currentUsername . '.pdf');;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memberships im File Name hier auch übersetzen? @DieMichii was ist ein Guter Dateiname für eine Gremienbescheinigung die wie im Chat aussieht?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p.s. man kann auch an die Übersetzung parameter (wie den Namen) übergeben. Das wäre hier vmtl. sinnig.

}
}
17 changes: 17 additions & 0 deletions app/Livewire/Realm/ListMembers.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -87,4 +88,20 @@ public function close(): void
$this->showDeleteModal = false;
unset($this->deleteMemberName, $this->deleteMemberUsername);
}

public function exportPdf($username)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sehe ich das Richtig, das es einmal das PDF pro Person und das andere mal das PDF pro Rolle / Realm ist?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Falls du dich auf den Zugriff aus unterschiedlichen Komponenten auf diese Funktion beziehst, dann ist der einzige Unterschied, dass im Profil kein Realm mitgegeben werden kann, da die Nutzer nicht nur einem einzigen Realm zugeordnet sein können. Beim Export über die Mitglieder-Liste eines Realms kann der aktuelle Realm für den PDF-Export mitgegeben werden, sodass auch die Studischaft im Dokument angezeigt werden kann.

{
$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');;
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions lang/de/profile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

return [
'activity' => 'Tätigkeit',
'activities' => 'Tätigkeiten',
'comment' => 'Bemerkung',
'committee' => 'Gremium',
'decision' => 'Beschluss',
'exportAsPdf' => 'Als PDF exportieren',
'from' => 'von',
'in' => 'in',
'memberships' => 'Mitgliedschaften',
'membershipsAsPdf' => 'Mitgliedschaften als PDF',
'role' => 'Rolle',
'showOnlyActiveMemberships' => 'Zeige nur aktive Mitgliedschaften',
'today' => 'heute',
'until' => 'bis',
];
18 changes: 18 additions & 0 deletions lang/en/profile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

return [
'activity' => 'Activity',
'activities' => 'Activities',
'comment' => 'Comment',
'committee' => 'Committee',
'decision' => 'Decision',
'exportAsPdf' => 'Export as PDF',
'from' => 'from',
'in' => 'in',
'memberships' => 'Memberships',
'membershipsAsPdf' => 'Memberships as PDF',
'role' => 'Role',
'showOnlyActiveMemberships' => 'Show only active memberships',
'today' => 'today',
'until' => 'until',
];
7 changes: 2 additions & 5 deletions resources/views/components/navigation.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@
</button>
</x-slot>
<x-slot name="content">
<x-dropdown-link :href="route('profile')">{{ __('Profile') }}</x-dropdown-link>
<x-dropdown-link :href="route('password.change')">{{ __('Change Password') }}</x-dropdown-link>

<hr class="h-px my-1 mx-1 bg-gray-200 border-0">
<x-dropdown-link :href="route('profile', ['username' => auth()->user()->username])">{{ __('Profile') }}</x-dropdown-link>

<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
Expand Down Expand Up @@ -141,7 +138,7 @@
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<x-responsive-nav-link :active="request()->routeIs('profile')"
:href="route('profile')">
:href="route('profile', ['username' => auth()->user()->username])">
{{ __('Profil') }}
</x-responsive-nav-link>
<div>
Expand Down
9 changes: 6 additions & 3 deletions resources/views/livewire/change-password.blade.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<x-livewire-form>
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Change Password') }}</h1>
<div class="w-full">
<div class="mb-4 -mx-6 -mt-6 px-6 flex border-b border-zinc-200 gap-3">
<a wire:navigate href="{{ route('profile', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('Profile') }}</a>
<a wire:navigate href="{{ route('profile.memberships', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('profile.memberships') }}</a>
<a wire:navigate href="{{ route('password.change', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-indigo-400 font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-indigo-400 focus:outline-none focus:text-gray-700 focus:border-indigo-400 transition duration-150 ease-in-out">{{ __('Change Password') }}</a>
</div>
</div>

<x-input.group type="password" wire:model="password" :label="__('Password')">
<x-slot:help>{{ __('user.help.password') }}</x-slot:help>
</x-input.group>
Expand Down
9 changes: 6 additions & 3 deletions resources/views/livewire/profile.blade.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<div>
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Profile') }}</h1>
<div class="w-full">
<div class="mb-4 -mx-6 -mt-6 px-6 flex border-b border-zinc-200 gap-3">
<a wire:navigate href="{{ route('profile', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-indigo-400 font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-indigo-400 focus:outline-none focus:text-gray-700 focus:border-indigo-400 transition duration-150 ease-in-out">{{ __('Profile') }}</a>
<a wire:navigate href="{{ route('profile.memberships', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('profile.memberships') }}</a>
<a wire:navigate href="{{ route('password.change', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('Change Password') }}</a>
</div>
</div>

<x-livewire-form :abort_route="null" wire:submit="save">
<div class="mt-6 sm:mt-5">
<x-input.group disabled :label="__('Username')" wire:model="uid">
Expand Down
77 changes: 77 additions & 0 deletions resources/views/livewire/profile/memberships.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<div>
<div class="w-full">
<div class="mb-4 -mx-6 -mt-6 px-6 flex border-b border-zinc-200 gap-3">
<a wire:navigate href="{{ route('profile', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('Profile') }}</a>
<a wire:navigate href="{{ route('profile.memberships', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-indigo-400 font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-indigo-400 focus:outline-none focus:text-gray-700 focus:border-indigo-400 transition duration-150 ease-in-out">{{ __('profile.memberships') }}</a>
<a wire:navigate href="{{ route('password.change', ['username' => $currentUsername]) }}" class="inline-flex items-center gap-x-1.5 px-2 pt-4 pb-3 border-b-2 border-transparent font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">{{ __('Change Password') }}</a>
</div>
</div>

<div>
<div class="mt-6 flex">
<div class="ml-auto">
<x-button.primary wire:click="exportPdf">{{ __('profile.exportAsPdf') }}</x-button.primary>
</div>
</div>
<div class="mt-6 mb-6">
<input id="showOnlyActive" type="checkbox" wire:model.change="showOnlyActive" class="rounded-sm border-indigo-400">
<label for="showOnlyActive" class="ml-4">{{ __('profile.showOnlyActiveMemberships') }}</label>
</div>
<x-table>
<x-slot name="head">
<x-table.heading>
{{ __('profile.role') }}
</x-table.heading>
<x-table.heading>
{{ __('profile.committee') }}
</x-table.heading>
<x-table.heading>
{{ __('profile.from') }}
</x-table.heading>
<x-table.heading>
{{ __('profile.until') }}
</x-table.heading>
<x-table.heading>
{{ __('profile.decision') }}
</x-table.heading>
<x-table.heading>
{{ __('profile.comment') }}
</x-table.heading>
</x-slot>
@forelse($memberships as $row)
<x-table.row>
<x-table.cell>
{{ $row['role']->getFirstAttribute('description') }}
</x-table.cell>
<x-table.cell>
{{ $row['role']->committee()->getFirstAttribute('description') }}
</x-table.cell>
<x-table.cell>
{{ \Carbon\Carbon::parse($row['from'])->format('Y-m-d') }}
</x-table.cell>
<x-table.cell>
@if ($row['until'] != '')
{{ \Carbon\Carbon::parse($row['until'])->format('Y-m-d') }}
@else
{{ __('profile.today') }}
@endif
</x-table.cell>
<x-table.cell>
{{ \Carbon\Carbon::parse($row['decided'])->format('Y-m-d') }}
</x-table.cell>
<x-table.cell>
{{ $row['comment'] }}
</x-table.cell>
</x-table.row>
@empty
<x-table.row>
<x-table.cell colspan="4">
<div class="flex justify-center item-center">
<span class="text-gray-400 text-xl py-2 font-medium">{{ __('groups.no_roles_found') }}</span>
</div>
</x-table.cell>
</x-table.row>
@endforelse
</x-table>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<p class="mt-2 text-sm text-gray-700">{{ __('realms.dashboard.explanation', ['name' => $name]) }}</p>
</div>
<div class="-mx-6 -mb-6 mt-10 border-t divide-y-1 divide-gray-200 overflow-hidden rounded-b-lg bg-gray-200 sm:grid sm:grid-cols-2 sm:gap-px sm:divide-y-0">
<x-dashboard-card :href="route('profile')" color="bg-yellow-100 text-yellow-700" icon="fas-user-gear">
<x-dashboard-card :href="route('profile', ['username' => auth()->user()->username])" color="bg-yellow-100 text-yellow-700" icon="fas-user-gear">
<x-slot:headline>
{{ __('realms.dashboard.profile_heading', ['name' => $name]) }}
</x-slot:headline>
Expand Down
29 changes: 23 additions & 6 deletions resources/views/livewire/realm/members.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,31 @@
</x-slot>
@forelse($realm_members as $realm_member)
<x-table.row>
<x-table.cell>{{ $realm_member->cn[0] }}</x-table.cell>
<x-table.cell>
@if (auth()->user()->can('superadmin', \App\Ldap\User::class))
<a wire:navigate href="{{ route('profile', ['username' => $realm_member->uid[0]]) }}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ich weiß das es schon so war, aber hier ist es vllt besser das native getFirstAttribute von LdapRecord zu verwenden. Der aktuelle Code würde explodieren falls uid bspw kein Array wäre

{{ $realm_member->cn[0] }}
</a>
@else
{{ $realm_member->cn[0] }}
@endif
</x-table.cell>
<x-table.cell>{{ $realm_member->uid[0] }}</x-table.cell>
<x-table.cell>
<x-button.link-danger
icon-leading="fas-triangle-exclamation"
:disabled="auth()->user()->cannot('remove_member', $community)"
wire:click="deletePrepare('{{ $realm_member->uid[0] }}')">{{ __('Remove Member') }}
</x-button.link-danger>
<div class="flex gap-3">
<x-button.link-primary
wire:click="exportPdf('{{ $realm_member->uid[0] }}')"
:disabled="auth()->user()->cannot('edit', $community)"
class="ml-auto"
>
{{ __('profile.membershipsAsPdf') }}
</x-button.link-primary>
<x-button.link-danger
icon-leading="fas-triangle-exclamation"
:disabled="auth()->user()->cannot('remove_member', $community)"
wire:click="deletePrepare('{{ $realm_member->uid[0] }}')">{{ __('Remove Member') }}
</x-button.link-danger>
</div>
</x-table.cell>
</x-table.row>
@empty
Expand Down
Loading