Skip to content

Commit

Permalink
Add Group and User Sync to IDP over Nextcloud
Browse files Browse the repository at this point in the history
  • Loading branch information
Thiritin committed May 26, 2024
1 parent e38820b commit 8b8cfb0
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 2 deletions.
1 change: 1 addition & 0 deletions app/Filament/Resources/GroupResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static function form(Form $form): Form
->required(),
TextInput::make('nextcloud_folder_name')
->label('Nextcloud Folder Name')
->required(fn(Group $record) => !empty($record->nextcloud_folder_id))
->hint('Leave empty if the group should not be allowed to access Nextcloud.')
->unique('groups', 'nextcloud_folder_name', ignoreRecord: true),
Textarea::make('description')->rows(5),
Expand Down
29 changes: 29 additions & 0 deletions app/Observers/GroupObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Enums\GroupUserLevel;
use App\Models\Group;
use App\Services\NextcloudService;
use Illuminate\Support\Facades\Auth;

class GroupObserver
Expand All @@ -16,4 +17,32 @@ public function created(Group $group)
]);
}
}

public function updated(Group $group): void
{
NextcloudService::checkUserExists("test22222");
if ($group->isDirty('nextcloud_folder_name')) {
// Update or create the folder via nextcloud
if ($group->nextcloud_folder_id) {
NextcloudService::renameFolder($group->nextcloud_folder_id, $group->nextcloud_folder_name);
} else {
NextcloudService::createGroup($group->hashid);
$group->nextcloud_folder_id = NextcloudService::createFolder($group->nextcloud_folder_name,
$group->hashid);
$group->save();
NextcloudService::setDisplayName($group->hashid, $group->name);
// Add all users to the group
$group->users->each(fn($user) => NextcloudService::addUserToGroup($group, $user));
// Set Admin & Owner to aclmanagers
$group->users->filter(fn($user) => in_array($user->pivot->level,
[GroupUserLevel::Admin, GroupUserLevel::Owner]))
->each(fn($user) => NextcloudService::setManageAcl($group, $user, true));

}
}
if ($group->isDirty('name')) {
// Update the display name of the group
NextcloudService::setDisplayName($group->hashid, $group->name);
}
}
}
19 changes: 19 additions & 0 deletions app/Observers/GroupUserObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace App\Observers;

use App\Enums\GroupTypeEnum;
use App\Enums\GroupUserLevel;
use App\Jobs\CheckStaffGroupMembershipJob;
use App\Models\GroupUser;
use App\Services\NextcloudService;

class GroupUserObserver
{
Expand All @@ -13,17 +15,34 @@ public function created(GroupUser $groupUser): void
if ($groupUser->group->type === GroupTypeEnum::Department) {
CheckStaffGroupMembershipJob::dispatch($groupUser->user);
}
if ($groupUser->group->nextcloud_folder_name) {
NextcloudService::addUserToGroup($groupUser->group, $groupUser->user);
$allowAclManagement = in_array($groupUser->level, [GroupUserLevel::Admin, GroupUserLevel::Owner]);
if ($allowAclManagement) {
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, $allowAclManagement);
}
}
}

public function updated(GroupUser $groupUser): void
{
if ($groupUser->group->nextcloud_folder_name) {
if ($groupUser->isDirty('level')) {
$allowAclManagement = in_array($groupUser->level, [GroupUserLevel::Admin, GroupUserLevel::Owner]);
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, $allowAclManagement);
}
}
}

public function deleted(GroupUser $groupUser): void
{
if ($groupUser->group->type === GroupTypeEnum::Department) {
CheckStaffGroupMembershipJob::dispatch($groupUser->user);
}
if ($groupUser->group->nextcloud_folder_name) {
NextcloudService::removeUserFromGroup($groupUser->group, $groupUser->user);
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, false);
}
}

public function restored(GroupUser $groupUser): void
Expand Down
6 changes: 6 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ public function boot()
return Http::baseUrl(config('services.hydra.public'));
});

Http::macro('nextcloud', function () {
return Http::baseUrl(config('services.nextcloud.baseUrl'))
->withHeader('OCS-APIRequest', "true")
->withBasicAuth(config('services.nextcloud.username'), config('services.nextcloud.password'));
});

Filament::serving(function () {
Filament::registerUserMenuItems([
'logout-everywhere' => UserMenuItem::make()
Expand Down
107 changes: 107 additions & 0 deletions app/Services/NextcloudService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace App\Services;

use App\Models\Group;
use App\Models\User;
use Illuminate\Support\Facades\Http;

class NextcloudService
{
public static function createGroup($groupId)
{
Http::nextcloud()->post("/ocs/v1.php/cloud/groups", [
"groupid" => $groupId,
])->throw();
return $groupId;
}

public static function setDisplayName($groupId, $displayName)
{
Http::nextcloud()->put("https://cloud.eurofurence.org/ocs/v2.php/cloud/groups/{$groupId}", [
'key' => 'displayname',
'value' => $displayName,
])->throw();
}

public static function checkUserExists($userId): bool
{
$res = Http::nextcloud()->get("ocs/v2.php/cloud/users/".$userId)->throwIfServerError();
if ($res->notFound()) {
return false;
}
if ($res->ok()) {
return true;
}
$res->throw();
}

public static function addUserToGroup(Group $group, User $user)
{
// Check user
if (!self::checkUserExists($user->hashid)) {
self::createUser($user); // Create user also adds groups so we don't need to add them here
} else {
Http::nextcloud()->post("ocs/v2.php/cloud/users/{$user->hashid}/groups", [
"groupid" => $group->hashid,
])->throw();
}
}

public static function removeUserFromGroup(Group $group, User $user)
{
if (!self::checkUserExists($user->hashid)) {
return;
}
Http::nextcloud()->delete("ocs/v2.php/cloud/users/{$user->hashid}/groups?groupid={$group->hashid}")->throw();
}

public static function setManageAcl(Group $group, User $user, bool $allow): void
{
Http::nextcloud()->post("apps/groupfolders/folders/{$group->nextcloud_folder_id}/manageACL", [
'mappingId' => $user->hashid,
'mappingType' => 'user',
'manageAcl' => $allow ? '1' : '0',
])->throwIfServerError();
}

public static function createUser(User $user)
{
Http::nextcloud()->post("ocs/v2.php/cloud/users", [
'displayName' => $user->name,
'email' => $user->email,
'groups' => $user->groups()->whereNotNull('nextcloud_folder_name')->get()->pluck('hashid')->toArray(),
'language' => 'en',
'password' => '',
'quota' => 'default',
'subadmin' => [],
'userid' => $user->hashid,
])->throw();
}

public static function createFolder(string $folderName, string $groupId): int
{
$response = Http::nextcloud()->post("apps/groupfolders/folders", [
"mountpoint" => $folderName,
])->throw();
$xml = simplexml_load_string($response->body());

// enable acl for group (we have that enabled for all groups)
Http::nextcloud()->post("apps/groupfolders/folders/{$xml->data->id}/acl", [
"acl" => 1,
])->throw();
// add group to folder apps/groupfolders/folders/$folderId/groups/$groupId
Http::nextcloud()->post("apps/groupfolders/folders/{$xml->data->id}/groups", [
"group" => $groupId,
])->throw();
return (int) $xml->data->id;
}

public static function renameFolder(int $folderId, string $folderName): void
{
Http::nextcloud()->post("apps/groupfolders/folders/{$folderId}/mountpoint", [
"mountpoint" => $folderName,
])->throw();
}

}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"spatie/laravel-permission": "^6.2.0",
"spatie/laravel-query-builder": "^5.1",
"spatie/laravel-translatable": "^6",
"tightenco/ziggy": "^1.0"
"tightenco/ziggy": "^1.0",
"ext-simplexml": "*"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
Expand Down
5 changes: 5 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,10 @@
'yubikey' => [
'client_id' => env('YUBICO_CLIENT_ID'),
'secret_key' => env('YUBICO_SECRET_KEY'),
],
'nextcloud' => [
'baseUrl' => env('NEXTCLOUD_BASE_URL'),
'username' => env('NEXTCLOUD_USERNAME'),
'password' => env('NEXTCLOUD_PASSWORD'),
]
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

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

return new class extends Migration {
public function up(): void
{
Schema::table('groups', function (Blueprint $table) {
$table->unsignedInteger('nextcloud_folder_id')->after('nextcloud_folder_name')->nullable();
// nextcloud group id
$table->string('nextcloud_group_id')->after('nextcloud_folder_id')->nullable();
});
}

public function down(): void
{
Schema::table('groups', function (Blueprint $table) {
$table->dropColumn('nextcloud_folder_id');
$table->dropColumn('nextcloud_group_id');
});
}
};
2 changes: 1 addition & 1 deletion resources/js/Pages/Auth/Register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
:invalid="form.invalid('username')"
v-model.trim.lazy="form.username"
/>
<InlineMessage v-if="form.invalid('username')" severity="error">{{ form.errors.username }}
<InlineMessage v-if="form.invalid('username')" seveGrity="error">{{ form.errors.username }}
</InlineMessage>
</div>
<div class="flex flex-col gap-2">
Expand Down

0 comments on commit 8b8cfb0

Please sign in to comment.