- Introduced `StoreURLAppForm` and `StoreURLAppData` for form handling and data transformation. - Added `UrlAppService` to streamline URL app creation with settings like `accessUrl` and `isConnectedToMicrosoft`. - Enhanced user access controls with role-based permissions using `UserAccessTypeEnum`. - Updated UI for URL app creation with support for logos and dynamic role selection. - Implemented feature tests to validate URL app creation and access URL formatting.
146 lines
4.7 KiB
PHP
146 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Data\ConnectedApp\ConnectedAppData;
|
|
use App\Data\Ui\Dashboard\ServiceAppDto;
|
|
use App\Enums\ConnectedAppPermissonEnum;
|
|
use App\Helpers\AppPermission;
|
|
use App\Models\{ConnectedApp, RestrictedUserPermission, Role, User};
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class UserAppAccessService
|
|
{
|
|
/**
|
|
* Get all active services for the user as a collection of DTOs.
|
|
*
|
|
* @return Collection<int, ServiceAppDto>
|
|
*/
|
|
public function getActiveServices(User $user): Collection
|
|
{
|
|
$user->loadMissing(['roles.apps.protocol', 'roles.apps.provider', 'roles.permissions']);
|
|
|
|
$restrictedPermissions = $this->getRestrictedPermissions($user);
|
|
|
|
/** @var Collection<int, ServiceAppDto> $appsMap */
|
|
$appsMap = collect();
|
|
|
|
/** @var Collection<int, Role> $roles */
|
|
$roles = $user->roles;
|
|
|
|
foreach ($roles as $role) {
|
|
/** @var Collection<int, ConnectedApp> $apps */
|
|
$apps = $role->apps;
|
|
|
|
foreach ($apps as $app) {
|
|
if ($this->isAppRestrictedForRole($role, $app, $restrictedPermissions)) {
|
|
continue;
|
|
}
|
|
|
|
/** @var mixed $pivot */
|
|
$pivot = $app->pivot;
|
|
$durationStr = (string) $pivot->duration;
|
|
|
|
$daysRemaining = $this->getDaysRemainingIfActive($durationStr);
|
|
|
|
// If null is returned, the app has expired.
|
|
if (null === $daysRemaining) {
|
|
continue;
|
|
}
|
|
|
|
$this->addOrUpdateAppInMap($appsMap, $app, $pivot->is_unlimited, $durationStr, $daysRemaining);
|
|
}
|
|
}
|
|
|
|
return $appsMap->values();
|
|
}
|
|
|
|
/**
|
|
* Retrieves a map of restricted permissions for the user, grouped by role ID.
|
|
*
|
|
* * @return array<int, array<int, int>>
|
|
*/
|
|
private function getRestrictedPermissions(User $user): array
|
|
{
|
|
return RestrictedUserPermission::query()
|
|
->where('user_id', $user->id)
|
|
->get()
|
|
->groupBy('role_id')
|
|
->map(fn ($item) => $item->pluck('permission_id')->all())
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* Checks if the app is restricted for the given role.
|
|
*
|
|
* * @param array<int, array<int, int>> $restrictedPermissions
|
|
*/
|
|
private function isAppRestrictedForRole(Role $role, ConnectedApp $app, array $restrictedPermissions): bool
|
|
{
|
|
$permissionName = AppPermission::name(ConnectedAppPermissonEnum::Use, ConnectedAppData::from($app));
|
|
$permission = $role->permissions->firstWhere('name', $permissionName);
|
|
|
|
if (! $permission) {
|
|
return false;
|
|
}
|
|
|
|
return isset($restrictedPermissions[$role->id])
|
|
&& in_array($permission->id, $restrictedPermissions[$role->id], true);
|
|
}
|
|
|
|
/**
|
|
* Calculates the days remaining. Returns null if the duration has expired.
|
|
*/
|
|
private function getDaysRemainingIfActive(string $durationStr): ?int
|
|
{
|
|
$duration = Carbon::parse($durationStr);
|
|
$now = now();
|
|
|
|
if ($duration->isPast()) {
|
|
return null;
|
|
}
|
|
|
|
$daysRemaining = (int) $now->diffInDays($duration, false);
|
|
|
|
if (0 === $daysRemaining && $now->lt($duration)) {
|
|
return 1;
|
|
}
|
|
|
|
return $daysRemaining;
|
|
}
|
|
|
|
/**
|
|
* Adds the app to the map or updates it if the new duration is greater than the existing one.
|
|
*
|
|
* * @param Collection<int, ServiceAppDto> $appsMap
|
|
*/
|
|
private function addOrUpdateAppInMap(Collection $appsMap, ConnectedApp $app, bool $isUnlimited, string $durationStr, int $daysRemaining): void
|
|
{
|
|
/** @var ServiceAppDto|null $existing */
|
|
$existing = $appsMap->get($app->id);
|
|
$duration = Carbon::parse($durationStr);
|
|
|
|
$protocolName = $app->protocol?->name ? mb_strtolower($app->protocol->name) : null;
|
|
$providerSlug = $app->provider?->slug ? mb_strtolower($app->provider->slug) : null;
|
|
|
|
if (! $existing || Carbon::parse($existing->duration)->lt($duration)) {
|
|
$appsMap->put($app->id, new ServiceAppDto(
|
|
id: $app->id,
|
|
name: $app->name,
|
|
slug: $app->slug,
|
|
duration: $durationStr,
|
|
isUnlimited: $isUnlimited,
|
|
daysRemaining: $daysRemaining,
|
|
isWarning: $daysRemaining <= 3,
|
|
protocolName: $protocolName,
|
|
providerSlug: $providerSlug,
|
|
accessUrl: data_get($app, 'settings.access_url'),
|
|
isConnectedToMicrosoft: data_get($app, 'settings.is_connected_to_microsoft', false),
|
|
));
|
|
}
|
|
}
|
|
}
|