Compare commits

...
12 Commits
100 changed files with 823 additions and 562 deletions
+1
View File
@@ -15,6 +15,7 @@ web_environment: []
nodejs_version: "18" nodejs_version: "18"
corepack_enable: false corepack_enable: false
disable_upload_dirs_warning: true disable_upload_dirs_warning: true
omit_containers: [ddev-ssh-agent]
xhgui_https_port: "8142" xhgui_https_port: "8142"
xhgui_http_port: "8143" xhgui_http_port: "8143"
+1 -1
View File
@@ -55,7 +55,7 @@ APP_BPN_DEBUG=false
# This hotel code will be assigned to admin users together with ROLE_HOTEL_MANAGER # This hotel code will be assigned to admin users together with ROLE_HOTEL_MANAGER
# in dev and staging environments for testing purposes # in dev and staging environments for testing purposes
APP_BPN_DEFAULT_HOTEL_CODE= APP_BPN_DEFAULT_HOTEL_CODE=
APP_BPN_CRM_ID_ADMIN=1292 APP_BPN_CRM_ID_TEAM_ADMIN=1484
APP_BPN_CRM_ID_MANAGER=1293 APP_BPN_CRM_ID_MANAGER=1293
APP_BPN_CRM_ID_TEAMER=1070 APP_BPN_CRM_ID_TEAMER=1070
Generated
+350 -312
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@ security:
property: email property: email
role_hierarchy: role_hierarchy:
ROLE_ADMIN: [ ROLE_ADMINISTRATIVE ] ROLE_TEAM_ADMIN: [ ROLE_ADMINISTRATIVE ]
ROLE_MANAGER: [ ROLE_ADMINISTRATIVE ] ROLE_MANAGER: [ ROLE_ADMINISTRATIVE ]
firewalls: firewalls:
+2 -1
View File
@@ -4,11 +4,12 @@ zenstruck_schedule:
mailer: mailer:
service: mailer service: mailer
default_to: [email protected] default_to: [email protected]
default_from: team@ep-reisen.de default_from: noreply@ep-reisen.de
subject_prefix: "[E&P-Team]" subject_prefix: "[E&P-Team]"
schedule_extensions: schedule_extensions:
email_on_failure: email_on_failure:
enabled: true
to: [email protected] to: [email protected]
tasks: tasks:
+2 -2
View File
@@ -1,6 +1,6 @@
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json # yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
parameters: parameters:
bpn_crm_id_admin: '%env(int:APP_BPN_CRM_ID_ADMIN)%' bpn_crm_id_team_admin: '%env(int:APP_BPN_CRM_ID_TEAM_ADMIN)%'
bpn_crm_id_manager: '%env(int:APP_BPN_CRM_ID_MANAGER)%' bpn_crm_id_manager: '%env(int:APP_BPN_CRM_ID_MANAGER)%'
bpn_crm_id_teamer: '%env(int:APP_BPN_CRM_ID_TEAMER)%' bpn_crm_id_teamer: '%env(int:APP_BPN_CRM_ID_TEAMER)%'
bpn_default_hotel_code: '%env(default::APP_BPN_DEFAULT_HOTEL_CODE)%' bpn_default_hotel_code: '%env(default::APP_BPN_DEFAULT_HOTEL_CODE)%'
@@ -162,7 +162,7 @@ services:
App\BusProNet\ResponseParser: App\BusProNet\ResponseParser:
arguments: arguments:
$options: $options:
bpn_crm_id_admin: '%bpn_crm_id_admin%' bpn_crm_id_team_admin: '%bpn_crm_id_team_admin%'
bpn_crm_id_manager: '%bpn_crm_id_manager%' bpn_crm_id_manager: '%bpn_crm_id_manager%'
bpn_crm_id_teamer: '%bpn_crm_id_teamer%' bpn_crm_id_teamer: '%bpn_crm_id_teamer%'
bpn_crm_house_manager_ids: '%bpn_crm_house_manager_ids%' bpn_crm_house_manager_ids: '%bpn_crm_house_manager_ids%'
+20 -36
View File
@@ -22,7 +22,7 @@ set('keep_releases', 3);
// Rsync options, mainly files/dirs to exclude // Rsync options, mainly files/dirs to exclude
$rsyncOptions = [ $rsyncOptions = [
'exclude' => [ 'exclude' => [
'.cursor', '.claude',
'.idea', '.idea',
'.vscode', '.vscode',
'.DS_Store', '.DS_Store',
@@ -70,44 +70,33 @@ $rsyncOptions = [
]; ];
host('prod') host('prod')
->setHostname('185.243.135.29')
->setRemoteUser('p704161')
->setForwardAgent(true)
->setSshMultiplexing(true)
->setDeployPath('/home/www/p704161/html/myep-team')
->set('writable_mode', 'chmod')
->set('http_user', 'p704161')
->set('bin/php', '/usr/local/bin/php')
->set('rsync_src', __DIR__)
->set('rsync', $rsyncOptions)
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://myep-team.ep-reisen.de')
;
host('staging')
->setHostname('185.243.135.29')
->setRemoteUser('p704161')
->setForwardAgent(true)
->setSshMultiplexing(true)
->setDeployPath('/home/www/p704161/html/myep-team-staging')
->set('writable_mode', 'chmod')
->set('http_user', 'p704161')
->set('bin/php', '/usr/local/bin/php')
->set('rsync_src', __DIR__)
->set('rsync', $rsyncOptions)
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://myep-team.ep-reisen.net')
;
host('hetzner')
->setHostname('dedi10193.your-server.de') ->setHostname('dedi10193.your-server.de')
->setRemoteUser('myepteam') ->setRemoteUser('myepteam')
->setForwardAgent(true) ->setForwardAgent(true)
->setSshMultiplexing(true) ->setSshMultiplexing(true)
->setDeployPath('/usr/home/myepteam/public_html/myep-team') ->setDeployPath('/usr/www/users/myepteam/prod')
->set('bin/php', '/usr/bin/php') ->set('bin/php', '/usr/bin/php')
->set('http_user', 'myepteam') ->set('http_user', 'myepteam')
->set('rsync_src', __DIR__) ->set('rsync_src', __DIR__)
->set('rsync', $rsyncOptions) ->set('rsync', $rsyncOptions)
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://myep-team.ep-reisen.net') ->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://team.ep-reisen.de')
;
host('staging')
->setHostname('dedi10193.your-server.de')
->setRemoteUser('myepteam')
->setForwardAgent(true)
->setSshMultiplexing(true)
->setDeployPath('/usr/www/users/myepteam/staging')
->set('bin/php', '/usr/bin/php')
->set('http_user', 'myepteam')
->set('rsync_src', __DIR__)
->set('rsync', $rsyncOptions)
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{current_path}}/public/ --web-url=https://team.ep-reisen.net --web-basic-auth=team:staging')
->add('shared_files', [
'public/.htaccess',
'public/.htpasswd',
])
; ;
task('deploy', [ task('deploy', [
@@ -116,7 +105,6 @@ task('deploy', [
'deploy:lock', 'deploy:lock',
'deploy:release', 'deploy:release',
'deploy:assets', 'deploy:assets',
'deploy:check-platform',
'rsync', 'rsync',
'deploy:shared', 'deploy:shared',
'deploy:writable', 'deploy:writable',
@@ -127,10 +115,6 @@ task('deploy', [
'deploy:stop-workers', 'deploy:stop-workers',
]); ]);
task('deploy:check-platform', function () {
runLocally('composer check-platform-reqs');
});
task('deploy:stop-workers', function () { task('deploy:stop-workers', function () {
run('{{bin/console}} messenger:stop-workers'); run('{{bin/console}} messenger:stop-workers');
}); });
+44 -12
View File
@@ -30,17 +30,35 @@ assign:
| Role | Label | Granted by | Revoked by | Hierarchy | | Role | Label | Granted by | Revoked by | Hierarchy |
|------|-------|-----------|------------|-----------| |------|-------|-----------|------------|-----------|
| `ROLE_ADMIN` | Admin | super admin, approving a CRM claim | the CRM, automatically | ⇒ `ROLE_ADMINISTRATIVE` | | `ROLE_TEAM_ADMIN` | Admin | super admin, approving a CRM claim | the CRM, automatically | ⇒ `ROLE_ADMINISTRATIVE` |
| `ROLE_MANAGER` | Reisemanager | super admin, approving a CRM claim | the CRM, automatically | ⇒ `ROLE_ADMINISTRATIVE` | | `ROLE_MANAGER` | Reisemanager | super admin, approving a CRM claim | the CRM, automatically | ⇒ `ROLE_ADMINISTRATIVE` |
| `ROLE_HOUSE_MANAGER` | Hausleitung | super admin, approving a CRM claim | the CRM, automatically | — | | `ROLE_HOUSE_MANAGER` | Hausleitung | super admin, approving a CRM claim | the CRM, automatically | — |
| `ROLE_TEAMER` | Teamer | the CRM, automatically | the CRM, automatically | — | | `ROLE_TEAMER` | Teamer | the CRM, automatically | the CRM, automatically | — |
### A note on the `TEAM_` prefix
`ROLE_TEAM_ADMIN`, `ROLE_TEAM_ADMIN_PENDING` and `ROLE_TEAM_SUPER_ADMIN` were renamed from
`ROLE_ADMIN`, `ROLE_ADMIN_PENDING` and `ROLE_SUPER_ADMIN`. The MyE&P identity provider is
shared with a sibling portal that uses `ROLE_ADMIN` for a different privilege, so the plain
name was ambiguous across the estate; the prefix makes it unambiguously *this* application's
admin. The German label is unchanged — it still reads "Admin" everywhere in the UI.
Two consequences worth remembering:
- **`ROLE_ADMINISTRATIVE` is a different role and was not renamed.** It is granted only by
the hierarchy, never stored, and it shares the old `ROLE_ADMIN` prefix — so any
search-and-replace over role names must match on a word boundary
(`ROLE_TEAM_ADMIN(?![A-Z_])`) or it will corrupt ~100 call sites silently.
- **`ELIGIBLE_ROLES` in `MyEpAuthenticator` is a wire contract**, not an internal name: it is
compared directly against the IdP's `roles` claim. It only works while MyE&P emits
`ROLE_TEAM_ADMIN`, so the two sides have to move together.
`User::PENDING_ROLES` holds a marker for each of the three administrative roles, keyed by `User::PENDING_ROLES` holds a marker for each of the three administrative roles, keyed by
the role it stands for: the role it stands for:
| Marker | Meaning | | Marker | Meaning |
|--------|---------| |--------|---------|
| `ROLE_ADMIN_PENDING` | the CRM claims this person is an admin, nobody has confirmed it | | `ROLE_TEAM_ADMIN_PENDING` | the CRM claims this person is an admin, nobody has confirmed it |
| `ROLE_MANAGER_PENDING` | likewise for Reisemanager | | `ROLE_MANAGER_PENDING` | likewise for Reisemanager |
| `ROLE_HOUSE_MANAGER_PENDING` | likewise for Hausleitung | | `ROLE_HOUSE_MANAGER_PENDING` | likewise for Hausleitung |
@@ -50,10 +68,10 @@ effects are cosmetic (rendered as "Admin (nicht freigeschaltet)") and organisati
put the user on the approval list). `ROLE_TEAMER` has no marker: it needs no approval. put the user on the approval list). `ROLE_TEAMER` has no marker: it needs no approval.
Two further roles are synthesized by `User::getRoles()` and never stored: `ROLE_USER` for Two further roles are synthesized by `User::getRoles()` and never stored: `ROLE_USER` for
everybody, and `ROLE_SUPER_ADMIN` when the separate `superAdmin` boolean column is set. A everybody, and `ROLE_TEAM_SUPER_ADMIN` when the separate `superAdmin` boolean column is set. A
validation callback (`User::validateSuperAdmin()`) refuses `superAdmin` without validation callback (`User::validateSuperAdmin()`) refuses `superAdmin` without
`ROLE_ADMIN` alongside it — super admin is an elevation, never a standalone grant. The sync `ROLE_TEAM_ADMIN` alongside it — super admin is an elevation, never a standalone grant. The sync
enforces the same rule from the other side: revoking `ROLE_ADMIN` clears the flag, or the one enforces the same rule from the other side: revoking `ROLE_TEAM_ADMIN` clears the flag, or the one
role that outranks every check in the application would outlive the role it depends on. role that outranks every check in the application would outlive the role it depends on.
### Storage and accessors ### Storage and accessors
@@ -63,7 +81,7 @@ slice it, and picking the right one matters:
| Accessor | Returns | | Accessor | Returns |
|----------|---------| |----------|---------|
| `getRoles()` | the column **plus** synthesized `ROLE_USER` / `ROLE_SUPER_ADMIN` — what Symfony authorises against | | `getRoles()` | the column **plus** synthesized `ROLE_USER` / `ROLE_TEAM_SUPER_ADMIN` — what Symfony authorises against |
| `getAssignedRoles()` | only the four real roles from the column — what the sync works on | | `getAssignedRoles()` | only the four real roles from the column — what the sync works on |
| `getPendingRoles()` | only the markers | | `getPendingRoles()` | only the markers |
| `getNominatedRoles()` | the roles behind those markers, as `role => label` — what an approver acts on | | `getNominatedRoles()` | the roles behind those markers, as `role => label` — what an approver acts on |
@@ -77,7 +95,7 @@ slice it, and picking the right one matters:
| CRM attribute | Recognised by | Sets | | CRM attribute | Recognised by | Sets |
|---------------|---------------|------| |---------------|---------------|------|
| admin | attribute id `%bpn_crm_id_admin%`, selected | `isAdmin` | | admin | attribute id `%bpn_crm_id_team_admin%`, selected | `isAdmin` |
| Reisemanager | attribute id `%bpn_crm_id_manager%`, selected | `isManager` | | Reisemanager | attribute id `%bpn_crm_id_manager%`, selected | `isManager` |
| teamer | attribute id `%bpn_crm_id_teamer%`, selected | `isTeamer` | | teamer | attribute id `%bpn_crm_id_teamer%`, selected | `isTeamer` |
| Hausleitung | attribute id listed in `%bpn_crm_house_manager_ids%`, selected | `isHouseManager` + the hotel code that id maps to | | Hausleitung | attribute id listed in `%bpn_crm_house_manager_ids%`, selected | `isHouseManager` + the hotel code that id maps to |
@@ -111,12 +129,26 @@ roles were revoked.
> >
> | Parameter | Attribute | > | Parameter | Attribute |
> |-----------|-----------| > |-----------|-----------|
> | `APP_BPN_CRM_ID_ADMIN` | `Admin` | > | `APP_BPN_CRM_ID_TEAM_ADMIN` | the team-admin selection — **not** the old portal-wide `Admin` (1292) |
> | `APP_BPN_CRM_ID_MANAGER` | `Manager` | > | `APP_BPN_CRM_ID_MANAGER` | `Manager` |
> | `APP_BPN_CRM_ID_TEAMER` | `E&P Teamer - allg. Merkmal` | > | `APP_BPN_CRM_ID_TEAMER` | `E&P Teamer - allg. Merkmal` |
> >
> Matching is by id and never by label, so `Preisrechner Admin` does not trip the admin flag. > Matching is by id and never by label, so `Preisrechner Admin` does not trip the admin flag.
> >
> `APP_BPN_CRM_ID_TEAM_ADMIN` was renamed from `APP_BPN_CRM_ID_ADMIN` with the
> `ROLE_TEAM_ADMIN` rename, and its **value has to change too**. The old value 1292 is the
> portal-wide admin selection, which still means `ROLE_ADMIN` in the sibling portal — keeping
> it would have left this app granting its admin off the very selection the rename was meant
> to stop sharing. It ships as `0` — a valid int that matches no attribute, so the container
> boots but nobody is granted the role — and must be set to the new selection's id.
>
> **This is a hard cutover.** `revokeUnclaimedRoles()` withdraws any granted role the CRM no
> longer claims, and `revokeSuperAdminWithoutRoleAdmin()` takes the super admin flag down with
> `ROLE_TEAM_ADMIN`. So the new selection must exist **and already be assigned to every admin**
> in BusPro before this is deployed; otherwise each of them is demoted on their next login and
> needs a super admin to re-approve. Admins who also hold `ROLE_TEAMER` degrade to teamer
> access; an admin without it is blocked outright by `disableForRevokedCrmRoles()`.
>
> `bpn_crm_house_manager_ids` (`config/services.yaml`) is deployment-critical for the same > `bpn_crm_house_manager_ids` (`config/services.yaml`) is deployment-critical for the same
> reason, and more sharply so: since roles are synced, an id missing from that map does not > reason, and more sharply so: since roles are synced, an id missing from that map does not
> merely fail to nominate a Hausleitung, it **revokes** the role from everyone holding it, one > merely fail to nominate a Hausleitung, it **revokes** the role from everyone holding it, one
@@ -137,7 +169,7 @@ roles were revoked.
`BpnAuthenticator::getOrCreateLocalUser()``UserDataHandler::createLocalUser()` writes `BpnAuthenticator::getOrCreateLocalUser()``UserDataHandler::createLocalUser()` writes
`collectRoles()` verbatim, together with the hotel codes from the Hausleitung attributes. `collectRoles()` verbatim, together with the hotel codes from the Hausleitung attributes.
A CRM admin who is not also a teamer therefore starts with `['ROLE_ADMIN_PENDING']` and no A CRM admin who is not also a teamer therefore starts with `['ROLE_TEAM_ADMIN_PENDING']` and no
privileges at all: they can authenticate, but `UserChecker` refuses the session until a privileges at all: they can authenticate, but `UserChecker` refuses the session until a
super admin approves them. super admin approves them.
@@ -148,7 +180,7 @@ the roles to **`syncRoles()`**, which is the whole policy in four steps:
1. **revoke** every granted role the CRM no longer claims. This is what makes BusPro the 1. **revoke** every granted role the CRM no longer claims. This is what makes BusPro the
source of truth, and it applies to `ROLE_TEAMER` as much as to the administrative roles. source of truth, and it applies to `ROLE_TEAMER` as much as to the administrative roles.
2. **clear the super admin flag** when `ROLE_ADMIN` was among them — `ROLE_SUPER_ADMIN` is 2. **clear the super admin flag** when `ROLE_TEAM_ADMIN` was among them — `ROLE_TEAM_SUPER_ADMIN` is
synthesized from a separate column and would otherwise survive its own precondition. synthesized from a separate column and would otherwise survive its own precondition.
3. **`refreshPendingRoles()`** recomputes the marker set from the current claims. A marker 3. **`refreshPendingRoles()`** recomputes the marker set from the current claims. A marker
whose real role is already granted is dropped — an approved role is never marked again. whose real role is already granted is dropped — an approved role is never marked again.
@@ -189,7 +221,7 @@ runs again on submit to catch a sync that revoked the claim while the dialog was
A denial is not recorded anywhere: as long as the CRM keeps claiming the role, the A denial is not recorded anywhere: as long as the CRM keeps claiming the role, the
nomination is back on the next login. nomination is back on the next login.
**Super admin** is only offered to somebody who already holds `ROLE_ADMIN` — approve first, **Super admin** is only offered to somebody who already holds `ROLE_TEAM_ADMIN` — approve first,
elevate afterwards. The one exception is a flag that outlived its role, which stays editable elevate afterwards. The one exception is a flag that outlived its role, which stays editable
so the account can be saved at all while `User::validateSuperAdmin()` is violated; the sync so the account can be saved at all while `User::validateSuperAdmin()` is violated; the sync
clears it (see 2), so it should never occur in practice. clears it (see 2), so it should never occur in practice.
@@ -243,7 +275,7 @@ administrative users, teamers are an admin's business:
| Surface | Who | Notes | | Surface | Who | Notes |
|---------|-----|-------| |---------|-----|-------|
| `/admin/teamer/disable-user/{uuid}` and `/administrative/teamer/enable-user/{uuid}` | `ROLE_ADMIN` | teamers; public reason mandatory, internal optional | | `/admin/teamer/disable-user/{uuid}` and `/administrative/teamer/enable-user/{uuid}` | `ROLE_TEAM_ADMIN` | teamers; public reason mandatory, internal optional |
| "Account gesperrt" checkbox on the user edit form | super admin (`UserVoter`) | everyone else; both reasons optional | | "Account gesperrt" checkbox on the user edit form | super admin (`UserVoter`) | everyone else; both reasons optional |
Both go through `User::setDisabled()`, which is a no-op when the state is unchanged — saving Both go through `User::setDisabled()`, which is a no-op when the state is unchanged — saving
+24 -5
View File
@@ -162,17 +162,36 @@ class ApiClient
$this->config['max_retries'] $this->config['max_retries']
); );
$this->send($socket, $body); $this->send($socket, $body);
$response = $this->receive($socket);
$this->disconnect($socket);
// message length (10 bytes) is prepended to actual message try {
$xml = substr($response, 10); // the length header is consumed by receive(), this is the payload
$xml = $this->receive($socket);
} finally {
$this->disconnect($socket);
}
if (true === $this->config['debug']) { if (true === $this->config['debug']) {
$this->dumpXmlToFile('response', $requestId, $xml); $this->dumpXmlToFile('response', $requestId, $xml);
} }
return $this->responseParser->parseXmlString($type, $xml); try {
return $this->responseParser->parseXmlString($type, $xml);
} catch (ResponseParserException $e) {
// Keep the evidence even outside debug mode: without the raw response a parse
// failure is not diagnosable after the fact. app:cleanup:xml-dumps prunes it.
if (true !== $this->config['debug']) {
$this->dumpXmlToFile('response', $requestId, $xml);
}
$this->logger->error('Unable to parse BusProNet response', [
'request_id' => $requestId,
'type' => $type,
'response_length' => strlen($xml),
'error_message' => $e->getMessage(),
]);
throw $e;
}
} }
private function dumpXmlToFile(string $type, string $requestId, string $body): void private function dumpXmlToFile(string $type, string $requestId, string $body): void
+47 -4
View File
@@ -58,15 +58,58 @@ trait ApiClientTrait
fwrite($socket, $send); fwrite($socket, $send);
} }
/**
* Reads a single response message. The protocol prepends the payload length as a
* 10 byte header, so read exactly that many bytes rather than guessing at EOF:
* a peer that closes mid-stream would otherwise yield a silently truncated body.
*
* @throws ApiClientException
*/
private function receive($socket): string private function receive($socket): string
{ {
$response = ''; $header = $this->readBytes($socket, 10);
while (false === feof($socket)) { if (10 !== strlen($header)) {
$response .= fread($socket, 4096); throw new ApiClientException(sprintf('Incomplete response header, got %d of 10 bytes', strlen($header)));
} }
return $response; $expectedLength = (int) trim($header);
if (1 > $expectedLength) {
throw new ApiClientException(sprintf('Response announced an empty body (header "%s")', trim($header)));
}
$body = $this->readBytes($socket, $expectedLength);
if (strlen($body) !== $expectedLength) {
throw new ApiClientException(sprintf('Truncated response, got %d of %d announced bytes', strlen($body), $expectedLength));
}
return $body;
}
/**
* @throws ApiClientException
*/
private function readBytes($socket, int $length): string
{
$buffer = '';
while (strlen($buffer) < $length && false === feof($socket)) {
$chunk = fread($socket, min(4096, $length - strlen($buffer)));
if (false === $chunk || '' === $chunk) {
if (true === (stream_get_meta_data($socket)['timed_out'] ?? false)) {
throw new ApiClientException(sprintf('Timed out reading response after %d of %d bytes', strlen($buffer), $length));
}
break;
}
$buffer .= $chunk;
}
return $buffer;
} }
private function disconnect($socket): void private function disconnect($socket): void
@@ -6,6 +6,7 @@ use App\BusProNet\ApiClient;
use App\BusProNet\ApiClientException; use App\BusProNet\ApiClientException;
use App\BusProNet\Model\Country; use App\BusProNet\Model\Country;
use App\BusProNet\Model\NotificationResponse; use App\BusProNet\Model\NotificationResponse;
use App\BusProNet\ResponseParserException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\ItemInterface;
@@ -35,7 +36,8 @@ class CountryDataProvider
return $response->getItems(); return $response->getItems();
}); });
} catch (ApiClientException $e) { } catch (ApiClientException|ResponseParserException $e) {
$this->logger->error('Unable to fetch country base data from BusProNet: '.$e->getMessage());
$countries = []; $countries = [];
} }
@@ -6,6 +6,7 @@ use App\BusProNet\ApiClient;
use App\BusProNet\ApiClientException; use App\BusProNet\ApiClientException;
use App\BusProNet\Model\Hotel; use App\BusProNet\Model\Hotel;
use App\BusProNet\Model\NotificationResponse; use App\BusProNet\Model\NotificationResponse;
use App\BusProNet\ResponseParserException;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CacheInterface;
@@ -36,7 +37,8 @@ class HotelDataProvider
return $response->getItems(); return $response->getItems();
}); });
} catch (ApiClientException|InvalidArgumentException $e) { } catch (ApiClientException|InvalidArgumentException|ResponseParserException $e) {
$this->logger->error('Unable to fetch hotel base data from BusProNet: '.$e->getMessage());
$hotels = []; $hotels = [];
} }
@@ -6,6 +6,7 @@ use App\BusProNet\ApiClient;
use App\BusProNet\ApiClientException; use App\BusProNet\ApiClientException;
use App\BusProNet\Model\NotificationResponse; use App\BusProNet\Model\NotificationResponse;
use App\BusProNet\Model\Pickup; use App\BusProNet\Model\Pickup;
use App\BusProNet\ResponseParserException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\ItemInterface;
@@ -35,7 +36,8 @@ class PickupDataProvider
return $response->getItems(); return $response->getItems();
}); });
} catch (ApiClientException $e) { } catch (ApiClientException|ResponseParserException $e) {
$this->logger->error('Unable to fetch pickup base data from BusProNet: '.$e->getMessage());
$pickups = []; $pickups = [];
} }
+49 -8
View File
@@ -30,10 +30,7 @@ class ResponseParser
*/ */
public function parseXmlString(string $type, string $content): mixed public function parseXmlString(string $type, string $content): mixed
{ {
$xml = simplexml_load_string($content); $xml = $this->loadXml($content);
if (false === $xml) {
throw new ResponseParserException('Unable to parse XML response');
}
// Override type when present in XML to catch error responses // Override type when present in XML to catch error responses
$responseType = $type; $responseType = $type;
@@ -65,7 +62,7 @@ class ResponseParser
return $this->createHotelsResponse($xml); return $this->createHotelsResponse($xml);
} }
throw new ResponseParserException('Unable to parse XML response'); throw new ResponseParserException(sprintf('Unrecognised BusProNet response type "%s"', $responseType));
} }
public function createNotificationResponse(\SimpleXMLElement $xml): NotificationResponse public function createNotificationResponse(\SimpleXMLElement $xml): NotificationResponse
@@ -200,7 +197,7 @@ class ResponseParser
$isHouseManager = true; $isHouseManager = true;
$hotelCodes[] = $houseManagerCode; $hotelCodes[] = $houseManagerCode;
} }
if ($this->config['bpn_crm_id_admin'] === $attribute->getId() && true === $attribute->isSelected()) { if ($this->config['bpn_crm_id_team_admin'] === $attribute->getId() && true === $attribute->isSelected()) {
$isAdmin = true; $isAdmin = true;
} }
if ($this->config['bpn_crm_id_manager'] === $attribute->getId() && true === $attribute->isSelected()) { if ($this->config['bpn_crm_id_manager'] === $attribute->getId() && true === $attribute->isSelected()) {
@@ -302,14 +299,58 @@ class ResponseParser
return new BaseDataResponse($hotels); return new BaseDataResponse($hotels);
} }
/**
* The BusProNet endpoint can answer with an empty or truncated body. Keep the libxml
* reason instead of collapsing every shape of broken response into one message.
*
* @throws ResponseParserException
*/
private function loadXml(string $content): \SimpleXMLElement
{
if ('' === trim($content)) {
throw new ResponseParserException(sprintf('Unable to parse XML response (%d bytes): empty response', strlen($content)));
}
$previousUseErrors = libxml_use_internal_errors(true);
libxml_clear_errors();
try {
$xml = simplexml_load_string($content);
if (false === $xml) {
throw new ResponseParserException(sprintf('Unable to parse XML response (%d bytes): %s', strlen($content), $this->describeLibxmlErrors()));
}
return $xml;
} finally {
libxml_clear_errors();
libxml_use_internal_errors($previousUseErrors);
}
}
private function describeLibxmlErrors(): string
{
$messages = [];
foreach (libxml_get_errors() as $error) {
$messages[] = sprintf('%s (line %d, column %d)', trim($error->message), $error->line, $error->column);
}
if ([] === $messages) {
return 'unknown XML error';
}
return implode('; ', array_unique($messages));
}
private function resolveOptions(array $options): array private function resolveOptions(array $options): array
{ {
$optionsResolver = new OptionsResolver(); $optionsResolver = new OptionsResolver();
$optionsResolver->setRequired(['bpn_crm_id_admin', 'bpn_crm_id_manager', 'bpn_crm_id_teamer', 'bpn_crm_house_manager_ids']); $optionsResolver->setRequired(['bpn_crm_id_team_admin', 'bpn_crm_id_manager', 'bpn_crm_id_teamer', 'bpn_crm_house_manager_ids']);
$optionsResolver->setDefaults([ $optionsResolver->setDefaults([
'bpn_default_hotel_code' => null, 'bpn_default_hotel_code' => null,
]); ]);
$optionsResolver->setAllowedTypes('bpn_crm_id_admin', 'int'); $optionsResolver->setAllowedTypes('bpn_crm_id_team_admin', 'int');
$optionsResolver->setAllowedTypes('bpn_crm_id_manager', 'int'); $optionsResolver->setAllowedTypes('bpn_crm_id_manager', 'int');
$optionsResolver->setAllowedTypes('bpn_crm_id_teamer', 'int'); $optionsResolver->setAllowedTypes('bpn_crm_id_teamer', 'int');
$optionsResolver->setAllowedTypes('bpn_crm_house_manager_ids', 'array'); $optionsResolver->setAllowedTypes('bpn_crm_house_manager_ids', 'array');
+7 -7
View File
@@ -69,7 +69,7 @@ class UserDataHandler
$claimedRoles = []; $claimedRoles = [];
if ($crmAttributes->isAdmin()) { if ($crmAttributes->isAdmin()) {
$claimedRoles[] = 'ROLE_ADMIN'; $claimedRoles[] = 'ROLE_TEAM_ADMIN';
} }
if ($crmAttributes->isManager()) { if ($crmAttributes->isManager()) {
@@ -370,7 +370,7 @@ class UserDataHandler
* The whole policy, in the order it has to run: * The whole policy, in the order it has to run:
* *
* 1. revoke what is no longer claimed - the identity source leads; * 1. revoke what is no longer claimed - the identity source leads;
* 2. drop the super admin flag along with ROLE_ADMIN, or the highest privilege in the * 2. drop the super admin flag along with ROLE_TEAM_ADMIN, or the highest privilege in the
* application would outlive the role it depends on; * application would outlive the role it depends on;
* 3. refresh the pending markers, after the revocation so that a role just revoked is * 3. refresh the pending markers, after the revocation so that a role just revoked is
* not immediately marked again - it is unclaimed in both steps; * not immediately marked again - it is unclaimed in both steps;
@@ -424,7 +424,7 @@ class UserDataHandler
* *
* Only the roles of User::ROLES are touched: getAssignedRoles() excludes the pending * Only the roles of User::ROLES are touched: getAssignedRoles() excludes the pending
* markers as well as the implicit ROLE_USER, and the markers are dealt with by * markers as well as the implicit ROLE_USER, and the markers are dealt with by
* refreshPendingRoles(). ROLE_SUPER_ADMIN is not a stored role at all but a flag, so * refreshPendingRoles(). ROLE_TEAM_SUPER_ADMIN is not a stored role at all but a flag, so
* it is handled separately below. * it is handled separately below.
* *
* @param string[] $claimedRoles * @param string[] $claimedRoles
@@ -450,22 +450,22 @@ class UserDataHandler
} }
/** /**
* Takes the super admin flag down with ROLE_ADMIN. * Takes the super admin flag down with ROLE_TEAM_ADMIN.
* *
* The flag is stored on its own and getRoles() turns it into ROLE_SUPER_ADMIN whatever * The flag is stored on its own and getRoles() turns it into ROLE_TEAM_SUPER_ADMIN whatever
* else the user holds, so without this a person the CRM no longer calls an admin would * else the user holds, so without this a person the CRM no longer calls an admin would
* keep the one role that outranks every check in the application. User::validateSuperAdmin() * keep the one role that outranks every check in the application. User::validateSuperAdmin()
* enforces the same rule on the edit form, but only there. * enforces the same rule on the edit form, but only there.
*/ */
private function revokeSuperAdminWithoutRoleAdmin(User $user): void private function revokeSuperAdminWithoutRoleAdmin(User $user): void
{ {
if (false === $user->isSuperAdmin() || true === $user->hasRole('ROLE_ADMIN')) { if (false === $user->isSuperAdmin() || true === $user->hasRole('ROLE_TEAM_ADMIN')) {
return; return;
} }
$user->setSuperAdmin(false); $user->setSuperAdmin(false);
$this->logger->info('Revoke super admin flag along with ROLE_ADMIN', [ $this->logger->info('Revoke super admin flag along with ROLE_TEAM_ADMIN', [
'user_id' => $user->getId(), 'user_id' => $user->getId(),
'user_email' => $user->getEmail(), 'user_email' => $user->getEmail(),
]); ]);
+21 -2
View File
@@ -56,11 +56,30 @@ class BpnImportCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
// Resolve the BusProNet base data once, before touching a single row. A destination
// cannot be written without its hotel, so an unavailable hotel list would otherwise
// skip every record and still report success. Holding both lists locally also keeps
// the loop off the providers: on a failed fetch nothing is cached, and a per-lookup
// ->get() would re-open the socket for every pickup of all 174 files.
$hotels = $this->hotelDataProvider->getAll();
$pickups = $this->pickupDataProvider->getAll();
if ([] === $hotels) {
$io->error('Hotel-Stammdaten konnten nicht von BusProNet geladen werden Import abgebrochen.');
$this->logger->error('BPN import aborted: hotel base data unavailable');
return Command::FAILURE;
}
$io->info('Found '.$totalCount.' XML files'); $io->info('Found '.$totalCount.' XML files');
$addedCount = 0; $addedCount = 0;
$updatedCount = 0; $updatedCount = 0;
$warnings = []; $warnings = [];
if ([] === $pickups) {
$warnings[] = 'Zustiegs-Stammdaten konnten nicht von BusProNet geladen werden';
}
$progressBar = $io->createProgressBar($totalCount); $progressBar = $io->createProgressBar($totalCount);
$progressBar->setFormat(" %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%\n %message%"); $progressBar->setFormat(" %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%\n %message%");
$progressBar->setMessage('Starting'); $progressBar->setMessage('Starting');
@@ -92,7 +111,7 @@ class BpnImportCommand extends Command
foreach ($destinationXml->xpath('zustiege/zustieg') as $pickupXml) { foreach ($destinationXml->xpath('zustiege/zustieg') as $pickupXml) {
$pickupBusProId = (int) $pickupXml->attributes()['idbuspro']; $pickupBusProId = (int) $pickupXml->attributes()['idbuspro'];
if ($pickupBusProId) { if ($pickupBusProId) {
if (null === $pickup = $this->pickupDataProvider->get($pickupBusProId)) { if (null === $pickup = $pickups[$pickupBusProId] ?? null) {
$warnings[] = 'Pickup with busProId '.$pickupBusProId.' not found'; $warnings[] = 'Pickup with busProId '.$pickupBusProId.' not found';
continue; continue;
} }
@@ -109,7 +128,7 @@ class BpnImportCommand extends Command
// Iterate over all hotel entries // Iterate over all hotel entries
foreach ($destinationXml->xpath('hotel') as $hotelXml) { foreach ($destinationXml->xpath('hotel') as $hotelXml) {
$hotelBusProId = (int) $hotelXml->attributes()['idbuspro']; $hotelBusProId = (int) $hotelXml->attributes()['idbuspro'];
if (null === $hotel = $this->hotelDataProvider->get($hotelBusProId)) { if (null === $hotel = $hotels[$hotelBusProId] ?? null) {
$warnings[] = 'Hotel with busProId '.$hotelBusProId.' not found'; $warnings[] = 'Hotel with busProId '.$hotelBusProId.' not found';
continue; continue;
} }
+2 -4
View File
@@ -91,10 +91,8 @@ class TeamerStatusCommand extends Command
$qb->expr()->eq('disposition.status', ':disposition_status') $qb->expr()->eq('disposition.status', ':disposition_status')
)) ))
->groupBy('teamer.id') ->groupBy('teamer.id')
->setParameters([ ->setParameter('teamer_status', Teamer::STATUS_NEW)
'teamer_status' => Teamer::STATUS_NEW, ->setParameter('disposition_status', Disposition::STATUS_COMPLETED)
'disposition_status' => Disposition::STATUS_COMPLETED,
])
->getQuery() ->getQuery()
->getResult() ->getResult()
; ;
@@ -16,7 +16,7 @@ class UserController extends AbstractController
} }
#[Route('/admin/autocomplete/user', name: 'app_admin_autocomplete_user')] #[Route('/admin/autocomplete/user', name: 'app_admin_autocomplete_user')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
try { try {
@@ -22,7 +22,7 @@ class ApproveController extends AbstractController
} }
#[Route('/admin/feedback/approve/{uuid}', name: 'app_admin_feedback_approve')] #[Route('/admin/feedback/approve/{uuid}', name: 'app_admin_feedback_approve')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Feedback $feedback, Request $request): Response public function index(Feedback $feedback, Request $request): Response
{ {
$form = $this->createForm(FeedbackApproveType::class, $feedback); $form = $this->createForm(FeedbackApproveType::class, $feedback);
@@ -24,7 +24,7 @@ class ProvideController extends AbstractController
} }
#[Route('/admin/feedback/provide', name: 'app_admin_feedback_provide')] #[Route('/admin/feedback/provide', name: 'app_admin_feedback_provide')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$form = $this->getFeedbackForm(); $form = $this->getFeedbackForm();
@@ -58,7 +58,7 @@ class ProvideController extends AbstractController
} }
#[Route('/admin/feedback/provide/form', name: 'app_admin_feedback_provide_form')] #[Route('/admin/feedback/provide/form', name: 'app_admin_feedback_provide_form')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function form(Request $request): Response public function form(Request $request): Response
{ {
$form = $this->getFeedbackForm(); $form = $this->getFeedbackForm();
+1 -1
View File
@@ -21,7 +21,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin', name: 'app_admin_index')] #[Route('/admin', name: 'app_admin_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
$applicationRepository = $this->entityManager->getRepository(Application::class); $applicationRepository = $this->entityManager->getRepository(Application::class);
+1 -1
View File
@@ -20,7 +20,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/log', name: 'app_admin_log_index')] #[Route('/admin/log', name: 'app_admin_log_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$qb = $this $qb = $this
@@ -22,7 +22,7 @@ class CreateController extends AbstractController
} }
#[Route('/admin/system/availability/create', name: 'app_admin_system_availability_create')] #[Route('/admin/system/availability/create', name: 'app_admin_system_availability_create')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$availability = new Availability(); $availability = new Availability();
@@ -21,7 +21,7 @@ class DeleteController extends AbstractController
} }
#[Route('/admin/system/availability/delete/{id}', name: 'app_admin_system_availability_delete', methods: ['POST'])] #[Route('/admin/system/availability/delete/{id}', name: 'app_admin_system_availability_delete', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Availability $availability, Request $request): Response public function index(Availability $availability, Request $request): Response
{ {
if (true === $request->isMethod('POST')) { if (true === $request->isMethod('POST')) {
@@ -22,7 +22,7 @@ class DuplicateController extends AbstractController
} }
#[Route('/admin/system/availability/duplicate/{id}', name: 'app_admin_system_availability_duplicate')] #[Route('/admin/system/availability/duplicate/{id}', name: 'app_admin_system_availability_duplicate')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Availability $availability, Request $request): Response public function index(Availability $availability, Request $request): Response
{ {
$copy = Availability::duplicate($availability); $copy = Availability::duplicate($availability);
@@ -22,7 +22,7 @@ class EditController extends AbstractController
} }
#[Route('/admin/system/availability/edit/{id}', name: 'app_admin_system_availability_edit')] #[Route('/admin/system/availability/edit/{id}', name: 'app_admin_system_availability_edit')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Availability $availability, Request $request): Response public function index(Availability $availability, Request $request): Response
{ {
$form = $this->createForm(AvailabilityType::class, $availability); $form = $this->createForm(AvailabilityType::class, $availability);
@@ -19,7 +19,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/availability', name: 'app_admin_system_availability_index')] #[Route('/admin/system/availability', name: 'app_admin_system_availability_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$query = $this $query = $this
@@ -28,7 +28,7 @@ class EditController extends AbstractController
} }
#[Route('/admin/system/email-text/edit/{key}', name: 'app_admin_system_email_text_edit')] #[Route('/admin/system/email-text/edit/{key}', name: 'app_admin_system_email_text_edit')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(EmailTextKey $key, Request $request): Response public function index(EmailTextKey $key, Request $request): Response
{ {
$definition = $this->catalog->get($key); $definition = $this->catalog->get($key);
@@ -18,7 +18,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/email-text', name: 'app_admin_system_email_text_index')] #[Route('/admin/system/email-text', name: 'app_admin_system_email_text_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
// The list is driven by the catalogue, not by the table: a mail that has never // The list is driven by the catalogue, not by the table: a mail that has never
@@ -25,7 +25,7 @@ class PreviewController extends AbstractController
* a real mail is being assembled. * a real mail is being assembled.
*/ */
#[Route('/admin/system/email-text/preview/{key}', name: 'app_admin_system_email_text_preview')] #[Route('/admin/system/email-text/preview/{key}', name: 'app_admin_system_email_text_preview')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(EmailTextKey $key): Response public function index(EmailTextKey $key): Response
{ {
$definition = $this->catalog->get($key); $definition = $this->catalog->get($key);
@@ -27,7 +27,7 @@ class PreviewDraftController extends AbstractController
* the submitted values instead of the stored ones. * the submitted values instead of the stored ones.
*/ */
#[Route('/admin/system/email-text/preview-draft/{key}', name: 'app_admin_system_email_text_preview_draft', methods: ['POST'])] #[Route('/admin/system/email-text/preview-draft/{key}', name: 'app_admin_system_email_text_preview_draft', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(EmailTextKey $key, Request $request): Response public function index(EmailTextKey $key, Request $request): Response
{ {
$definition = $this->catalog->get($key); $definition = $this->catalog->get($key);
@@ -25,7 +25,7 @@ class ResetController extends AbstractController
} }
#[Route('/admin/system/email-text/reset/{key}', name: 'app_admin_system_email_text_reset')] #[Route('/admin/system/email-text/reset/{key}', name: 'app_admin_system_email_text_reset')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(EmailTextKey $key, Request $request): Response public function index(EmailTextKey $key, Request $request): Response
{ {
$emailText = $this->emailTextRepository->findByKey($key); $emailText = $this->emailTextRepository->findByKey($key);
@@ -22,7 +22,7 @@ class CreateController extends AbstractController
} }
#[Route('/admin/system/fee/create', name: 'app_admin_system_fee_create')] #[Route('/admin/system/fee/create', name: 'app_admin_system_fee_create')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$fee = new Fee(); $fee = new Fee();
@@ -21,7 +21,7 @@ class DeleteController extends AbstractController
} }
#[Route('/admin/system/fee/delete/{id}', name: 'app_admin_system_fee_delete')] #[Route('/admin/system/fee/delete/{id}', name: 'app_admin_system_fee_delete')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Fee $fee, Request $request): Response public function index(Fee $fee, Request $request): Response
{ {
if (true === $request->isMethod('POST')) { if (true === $request->isMethod('POST')) {
@@ -22,7 +22,7 @@ class DuplicateController extends AbstractController
} }
#[Route('/admin/system/fee/duplicate/{id}', name: 'app_admin_system_fee_duplicate')] #[Route('/admin/system/fee/duplicate/{id}', name: 'app_admin_system_fee_duplicate')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Fee $fee, Request $request): Response public function index(Fee $fee, Request $request): Response
{ {
$copy = Fee::duplicate($fee); $copy = Fee::duplicate($fee);
@@ -22,7 +22,7 @@ class EditController extends AbstractController
} }
#[Route('/admin/system/fee/edit/{id}', name: 'app_admin_system_fee_edit')] #[Route('/admin/system/fee/edit/{id}', name: 'app_admin_system_fee_edit')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Fee $fee, Request $request): Response public function index(Fee $fee, Request $request): Response
{ {
$form = $this->createForm(FeeType::class, $fee); $form = $this->createForm(FeeType::class, $fee);
@@ -15,7 +15,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/fee', name: 'app_admin_system_fee_index')] #[Route('/admin/system/fee', name: 'app_admin_system_fee_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
$fees = $this->feeRepository->getList(); $fees = $this->feeRepository->getList();
@@ -21,7 +21,7 @@ class CreateController extends AbstractController
} }
#[Route('/admin/system/feedback-set/create', name: 'app_admin_system_feedback_set_create')] #[Route('/admin/system/feedback-set/create', name: 'app_admin_system_feedback_set_create')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$feedbackSet = new FeedbackSet(); $feedbackSet = new FeedbackSet();
@@ -21,7 +21,7 @@ class DeleteController extends AbstractController
} }
#[Route('/admin/system/feedback-set/delete/{id}', name: 'app_admin_system_feedback_set_delete')] #[Route('/admin/system/feedback-set/delete/{id}', name: 'app_admin_system_feedback_set_delete')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
#[IsGranted('DELETE', subject: 'feedbackSet')] #[IsGranted('DELETE', subject: 'feedbackSet')]
public function index(FeedbackSet $feedbackSet, Request $request): Response public function index(FeedbackSet $feedbackSet, Request $request): Response
{ {
@@ -21,7 +21,7 @@ class EditController extends AbstractController
} }
#[Route('/admin/system/feedback-set/edit/{id}', name: 'app_admin_system_feedback_set_edit')] #[Route('/admin/system/feedback-set/edit/{id}', name: 'app_admin_system_feedback_set_edit')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(FeedbackSet $feedbackSet, Request $request): Response public function index(FeedbackSet $feedbackSet, Request $request): Response
{ {
$form = $this->createForm(FeedbackSetType::class, $feedbackSet); $form = $this->createForm(FeedbackSetType::class, $feedbackSet);
@@ -15,7 +15,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/feedback-set', name: 'app_admin_system_feedback_set_index')] #[Route('/admin/system/feedback-set', name: 'app_admin_system_feedback_set_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
$feedbackSets = $this $feedbackSets = $this
@@ -21,7 +21,7 @@ class CreateController extends AbstractController
} }
#[Route('/admin/system/job-profile/create', name: 'app_admin_system_job_profile_create')] #[Route('/admin/system/job-profile/create', name: 'app_admin_system_job_profile_create')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$jobProfile = new JobProfile(); $jobProfile = new JobProfile();
@@ -21,7 +21,7 @@ class DeleteController extends AbstractController
} }
#[Route('/admin/system/job-profile/delete/{id}', name: 'app_admin_system_job_profile_delete')] #[Route('/admin/system/job-profile/delete/{id}', name: 'app_admin_system_job_profile_delete')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(JobProfile $jobProfile, Request $request): Response public function index(JobProfile $jobProfile, Request $request): Response
{ {
if (true === $request->isMethod('POST')) { if (true === $request->isMethod('POST')) {
@@ -21,7 +21,7 @@ class EditController extends AbstractController
} }
#[Route('/admin/system/job-profile/edit/{id}', name: 'app_admin_system_job_profile_edit')] #[Route('/admin/system/job-profile/edit/{id}', name: 'app_admin_system_job_profile_edit')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(JobProfile $jobProfile, Request $request): Response public function index(JobProfile $jobProfile, Request $request): Response
{ {
$form = $this->createForm(JobProfileType::class, $jobProfile); $form = $this->createForm(JobProfileType::class, $jobProfile);
@@ -15,7 +15,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/job-profile', name: 'app_admin_system_job_profile_index')] #[Route('/admin/system/job-profile', name: 'app_admin_system_job_profile_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
$jobProfiles = $this $jobProfiles = $this
@@ -22,7 +22,7 @@ class CreateController extends AbstractController
} }
#[Route('/admin/system/training/create', name: 'app_admin_system_training_create')] #[Route('/admin/system/training/create', name: 'app_admin_system_training_create')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$training = new Training(); $training = new Training();
@@ -21,7 +21,7 @@ class DeleteController extends AbstractController
} }
#[Route('/admin/system/training/delete/{id}', name: 'app_admin_system_training_delete')] #[Route('/admin/system/training/delete/{id}', name: 'app_admin_system_training_delete')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Training $training, Request $request): Response public function index(Training $training, Request $request): Response
{ {
if (true === $request->isMethod('POST')) { if (true === $request->isMethod('POST')) {
@@ -22,7 +22,7 @@ class EditController extends AbstractController
} }
#[Route('/admin/system/training/edit/{id}', name: 'app_admin_system_training_edit')] #[Route('/admin/system/training/edit/{id}', name: 'app_admin_system_training_edit')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Training $training, Request $request): Response public function index(Training $training, Request $request): Response
{ {
$form = $this->createForm(TrainingType::class, $training); $form = $this->createForm(TrainingType::class, $training);
@@ -15,7 +15,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/training', name: 'app_admin_system_training_index')] #[Route('/admin/system/training', name: 'app_admin_system_training_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
$trainings = $this $trainings = $this
@@ -15,7 +15,7 @@ class IndexController extends AbstractController
} }
#[Route('/admin/system/user', name: 'app_admin_system_user_index')] #[Route('/admin/system/user', name: 'app_admin_system_user_index')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(): Response public function index(): Response
{ {
$users = $this->userRepository->getAdministrativeUsers(); $users = $this->userRepository->getAdministrativeUsers();
@@ -11,7 +11,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class CrmSelectionsController extends AbstractController class CrmSelectionsController extends AbstractController
{ {
#[Route('/admin/teamer/crm-selections/{uuid}', name: 'app_admin_teamer_crm_selections')] #[Route('/admin/teamer/crm-selections/{uuid}', name: 'app_admin_teamer_crm_selections')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Teamer $teamer): Response public function index(Teamer $teamer): Response
{ {
return $this->render('admin/teamer/crm_selections.html.twig', [ return $this->render('admin/teamer/crm_selections.html.twig', [
@@ -23,7 +23,7 @@ class DeleteAccountController extends AbstractController
} }
#[Route('/admin/teamer/delete-account/{uuid}', name: 'app_admin_teamer_delete_account')] #[Route('/admin/teamer/delete-account/{uuid}', name: 'app_admin_teamer_delete_account')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Teamer $teamer, Request $request): Response public function index(Teamer $teamer, Request $request): Response
{ {
if (true === $request->isMethod(Request::METHOD_POST)) { if (true === $request->isMethod(Request::METHOD_POST)) {
@@ -44,7 +44,7 @@ class DeleteAccountController extends AbstractController
} }
#[Route('/admin/teamer/restore-account/{uuid}', name: 'app_admin_teamer_restore_account')] #[Route('/admin/teamer/restore-account/{uuid}', name: 'app_admin_teamer_restore_account')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function restore(Teamer $teamer, Request $request): Response public function restore(Teamer $teamer, Request $request): Response
{ {
if (true === $request->isMethod(Request::METHOD_POST)) { if (true === $request->isMethod(Request::METHOD_POST)) {
@@ -22,7 +22,7 @@ class DisableUserController extends AbstractController
} }
#[Route('/admin/teamer/disable-user/{uuid}', name: 'app_admin_teamer_disable_user')] #[Route('/admin/teamer/disable-user/{uuid}', name: 'app_admin_teamer_disable_user')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Teamer $teamer, Request $request): Response public function index(Teamer $teamer, Request $request): Response
{ {
$user = $teamer->getUser(); $user = $teamer->getUser();
@@ -50,7 +50,7 @@ class DisableUserController extends AbstractController
} }
#[Route('/administrative/teamer/enable-user/{uuid}', name: 'app_admin_teamer_enable_user')] #[Route('/administrative/teamer/enable-user/{uuid}', name: 'app_admin_teamer_enable_user')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function enable(Teamer $teamer, Request $request): Response public function enable(Teamer $teamer, Request $request): Response
{ {
$user = $teamer->getUser(); $user = $teamer->getUser();
@@ -39,7 +39,7 @@ class MailingController extends AbstractController
* the real send goes through the confirmation modal below. * the real send goes through the confirmation modal below.
*/ */
#[Route('/admin/teamer/mailing', name: 'app_admin_teamer_mailing')] #[Route('/admin/teamer/mailing', name: 'app_admin_teamer_mailing')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Request $request): Response public function index(Request $request): Response
{ {
$form = $this->createMailingForm($request); $form = $this->createMailingForm($request);
@@ -82,7 +82,7 @@ class MailingController extends AbstractController
* one that was parked. * one that was parked.
*/ */
#[Route('/admin/teamer/mailing/draft', name: 'app_admin_teamer_mailing_draft', methods: ['POST'])] #[Route('/admin/teamer/mailing/draft', name: 'app_admin_teamer_mailing_draft', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function draft(Request $request): Response public function draft(Request $request): Response
{ {
$mailingDto = $this->createMailingForm($request)->getData(); $mailingDto = $this->createMailingForm($request)->getData();
@@ -93,7 +93,7 @@ class MailingController extends AbstractController
} }
#[Route('/admin/teamer/mailing/discard', name: 'app_admin_teamer_mailing_discard')] #[Route('/admin/teamer/mailing/discard', name: 'app_admin_teamer_mailing_discard')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function discard(): Response public function discard(): Response
{ {
$this->draftHandler->resetDraft(); $this->draftHandler->resetDraft();
@@ -102,7 +102,7 @@ class MailingController extends AbstractController
} }
#[Route('/admin/teamer/mailing/confirm', name: 'app_admin_teamer_mailing_confirm', methods: ['POST'])] #[Route('/admin/teamer/mailing/confirm', name: 'app_admin_teamer_mailing_confirm', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function confirm(Request $request): Response public function confirm(Request $request): Response
{ {
$form = $this->createMailingForm($request); $form = $this->createMailingForm($request);
@@ -119,7 +119,7 @@ class MailingController extends AbstractController
} }
#[Route('/admin/teamer/mailing/send', name: 'app_admin_teamer_mailing_send', methods: ['POST'])] #[Route('/admin/teamer/mailing/send', name: 'app_admin_teamer_mailing_send', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function send(Request $request): Response public function send(Request $request): Response
{ {
$form = $this->createMailingForm($request); $form = $this->createMailingForm($request);
@@ -21,7 +21,7 @@ class RemarksController extends AbstractController
} }
#[Route('/admin/teamer/remarks/{uuid}', name: 'app_admin_teamer_remarks_internal')] #[Route('/admin/teamer/remarks/{uuid}', name: 'app_admin_teamer_remarks_internal')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Teamer $teamer, Request $request): Response public function index(Teamer $teamer, Request $request): Response
{ {
$returnUrl = $this->getReturnUrl($request, 'app_administrative_teamer_index'); $returnUrl = $this->getReturnUrl($request, 'app_administrative_teamer_index');
@@ -25,7 +25,7 @@ class SkillsController extends AbstractController
} }
#[Route('/admin/teamer/skills/{uuid}', name: 'app_admin_teamer_skills')] #[Route('/admin/teamer/skills/{uuid}', name: 'app_admin_teamer_skills')]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(Teamer $teamer, Request $request): Response public function index(Teamer $teamer, Request $request): Response
{ {
$trainings = $this->trainingRepository->getList(); $trainings = $this->trainingRepository->getList();
@@ -34,7 +34,7 @@ class CreateController extends AbstractController
path: '/admin/teamer/skills/training-attendance/create/{training_id}/{teamer_id}', path: '/admin/teamer/skills/training-attendance/create/{training_id}/{teamer_id}',
name: 'app_admin_teamer_skills_training_attendance_create' name: 'app_admin_teamer_skills_training_attendance_create'
)] )]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index( public function index(
#[MapEntity(mapping: ['training_id' => 'id'])] #[MapEntity(mapping: ['training_id' => 'id'])]
Training $training, Training $training,
@@ -30,7 +30,7 @@ class DeleteController extends AbstractController
path: '/admin/teamer/skills/training-attendance/delete/{uuid}', path: '/admin/teamer/skills/training-attendance/delete/{uuid}',
name: 'app_admin_teamer_skills_training_attendance_delete' name: 'app_admin_teamer_skills_training_attendance_delete'
)] )]
#[IsGranted('ROLE_ADMIN')] #[IsGranted('ROLE_TEAM_ADMIN')]
public function index(TrainingAttendance $attendance, Request $request): Response public function index(TrainingAttendance $attendance, Request $request): Response
{ {
if (true === $request->isMethod('POST')) { if (true === $request->isMethod('POST')) {
@@ -25,7 +25,7 @@ class CallOffController extends AbstractController
} }
#[Route('/administrative/assignment/call-off/{uuid}', name: 'app_administrative_assignment_call_off')] #[Route('/administrative/assignment/call-off/{uuid}', name: 'app_administrative_assignment_call_off')]
#[IsGranted('ROLE_ADMINISTRATIVE')] #[IsGranted('CALL_OFF', subject: 'assignment')]
public function index(Assignment $assignment, Request $request): Response public function index(Assignment $assignment, Request $request): Response
{ {
if (true === $request->isMethod('POST')) { if (true === $request->isMethod('POST')) {
@@ -11,6 +11,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class PublishController extends AbstractController class PublishController extends AbstractController
{ {
@@ -23,6 +24,7 @@ class PublishController extends AbstractController
} }
#[Route('/administrative/assignment/publish/{uuid}', name: 'app_administrative_assignment_publish')] #[Route('/administrative/assignment/publish/{uuid}', name: 'app_administrative_assignment_publish')]
#[IsGranted('PUBLISH', subject: 'assignment')]
public function index(Assignment $assignment, Request $request): Response public function index(Assignment $assignment, Request $request): Response
{ {
$returnUrl = $this->getReturnUrl($request, 'app_administrative_assignment_index'); $returnUrl = $this->getReturnUrl($request, 'app_administrative_assignment_index');
@@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class EditController extends AbstractController class EditController extends AbstractController
{ {
@@ -27,6 +28,7 @@ class EditController extends AbstractController
} }
#[Route('/administrative/destination/edit/{id}', name: 'app_administrative_system_destination_edit')] #[Route('/administrative/destination/edit/{id}', name: 'app_administrative_system_destination_edit')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function index(Destination $destination, Request $request): Response public function index(Destination $destination, Request $request): Response
{ {
$destinationDto = DestinationDto::fromEntity($destination); $destinationDto = DestinationDto::fromEntity($destination);
@@ -32,6 +32,7 @@ class IndexController extends AbstractController
} }
#[Route('/administrative/system/faq/sort', name: 'app_administrative_system_faq_sort', methods: ['POST'])] #[Route('/administrative/system/faq/sort', name: 'app_administrative_system_faq_sort', methods: ['POST'])]
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function sort(Request $request): JsonResponse public function sort(Request $request): JsonResponse
{ {
$data = json_decode($request->getContent(), true); $data = json_decode($request->getContent(), true);
@@ -55,6 +55,7 @@ class ApplicationFilterController extends AbstractController
} }
#[Route('/common/application/filter/reset', name: 'app_common_application_filter_reset')] #[Route('/common/application/filter/reset', name: 'app_common_application_filter_reset')]
#[IsGranted('ROLE_USER')]
public function reset(Request $request): Response public function reset(Request $request): Response
{ {
$this->filterHandler->resetFilterSettings(); $this->filterHandler->resetFilterSettings();
@@ -53,6 +53,7 @@ class AssignmentFilterController extends AbstractController
} }
#[Route('/common/assignment/filter/reset', name: 'app_common_assignment_filter_reset')] #[Route('/common/assignment/filter/reset', name: 'app_common_assignment_filter_reset')]
#[IsGranted('ROLE_USER')]
public function reset(Request $request): Response public function reset(Request $request): Response
{ {
$this->filterHandler->resetFilterSettings(); $this->filterHandler->resetFilterSettings();
@@ -47,6 +47,7 @@ class DocumentFilterController extends AbstractController
} }
#[Route('/common/document/filter/reset', name: 'app_common_document_filter_reset')] #[Route('/common/document/filter/reset', name: 'app_common_document_filter_reset')]
#[IsGranted('ROLE_USER')]
public function reset(Request $request): Response public function reset(Request $request): Response
{ {
$this->filterHandler->resetFilterSettings(); $this->filterHandler->resetFilterSettings();
@@ -39,6 +39,7 @@ class FeedbackFilterController extends AbstractController
} }
#[Route('/common/feedback/filter/reset', name: 'app_common_feedback_filter_reset')] #[Route('/common/feedback/filter/reset', name: 'app_common_feedback_filter_reset')]
#[IsGranted('ROLE_USER')]
public function reset(Request $request): Response public function reset(Request $request): Response
{ {
$this->filterHandler->resetFilterSettings(); $this->filterHandler->resetFilterSettings();
@@ -42,6 +42,7 @@ class TeamerFilterController extends AbstractController
} }
#[Route('/common/teamer/filter/reset', name: 'app_common_teamer_filter_reset')] #[Route('/common/teamer/filter/reset', name: 'app_common_teamer_filter_reset')]
#[IsGranted('ROLE_USER')]
public function reset(Request $request): Response public function reset(Request $request): Response
{ {
$this->filterHandler->resetFilterSettings(); $this->filterHandler->resetFilterSettings();
@@ -52,6 +52,7 @@ class TimelineFilterController extends AbstractController
} }
#[Route('/common/timeline/filter/reset', name: 'app_common_timeline_filter_reset')] #[Route('/common/timeline/filter/reset', name: 'app_common_timeline_filter_reset')]
#[IsGranted('ROLE_USER')]
public function reset(Request $request): Response public function reset(Request $request): Response
{ {
$this->filterHandler->resetFilterSettings(); $this->filterHandler->resetFilterSettings();
@@ -8,6 +8,7 @@ use App\Repository\UploadRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class IndexController extends AbstractController class IndexController extends AbstractController
{ {
@@ -18,6 +19,7 @@ class IndexController extends AbstractController
} }
#[Route('/management', name: 'app_manager_index')] #[Route('/management', name: 'app_manager_index')]
#[IsGranted('ROLE_MANAGER')]
public function index(): Response public function index(): Response
{ {
$dispositions = $this->dispositionRepository->getNew(); $dispositions = $this->dispositionRepository->getNew();
@@ -7,6 +7,7 @@ use App\Repository\ContactRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class ContactController extends AbstractController class ContactController extends AbstractController
{ {
@@ -15,6 +16,7 @@ class ContactController extends AbstractController
} }
#[Route('/teamer/contact', name: 'app_teamer_contact')] #[Route('/teamer/contact', name: 'app_teamer_contact')]
#[IsGranted('ROLE_USER')]
public function index(): Response public function index(): Response
{ {
$allContacts = $this $allContacts = $this
+7 -7
View File
@@ -22,7 +22,7 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
* Assignable roles and their labels. * Assignable roles and their labels.
*/ */
public const ROLES = [ public const ROLES = [
'ROLE_ADMIN' => 'Admin', 'ROLE_TEAM_ADMIN' => 'Team Admin',
'ROLE_MANAGER' => 'Reisemanager', 'ROLE_MANAGER' => 'Reisemanager',
'ROLE_HOUSE_MANAGER' => 'Hausleitung', 'ROLE_HOUSE_MANAGER' => 'Hausleitung',
'ROLE_TEAMER' => 'Teamer', 'ROLE_TEAMER' => 'Teamer',
@@ -35,7 +35,7 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
* a super admin. * a super admin.
*/ */
public const PENDING_ROLES = [ public const PENDING_ROLES = [
'ROLE_ADMIN' => 'ROLE_ADMIN_PENDING', 'ROLE_TEAM_ADMIN' => 'ROLE_TEAM_ADMIN_PENDING',
'ROLE_MANAGER' => 'ROLE_MANAGER_PENDING', 'ROLE_MANAGER' => 'ROLE_MANAGER_PENDING',
'ROLE_HOUSE_MANAGER' => 'ROLE_HOUSE_MANAGER_PENDING', 'ROLE_HOUSE_MANAGER' => 'ROLE_HOUSE_MANAGER_PENDING',
]; ];
@@ -194,7 +194,7 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
$roles = ['ROLE_USER', ...$this->roles]; $roles = ['ROLE_USER', ...$this->roles];
if (true === $this->isSuperAdmin()) { if (true === $this->isSuperAdmin()) {
$roles[] = 'ROLE_SUPER_ADMIN'; $roles[] = 'ROLE_TEAM_SUPER_ADMIN';
} }
return array_unique($roles); return array_unique($roles);
@@ -223,7 +223,7 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
/** /**
* The manually assignable roles held by the user, i.e. without the implicit ROLE_USER * The manually assignable roles held by the user, i.e. without the implicit ROLE_USER
* and ROLE_SUPER_ADMIN added by getRoles() and without any pending marker. Used to * and ROLE_TEAM_SUPER_ADMIN added by getRoles() and without any pending marker. Used to
* edit role assignments: saving them resolves the pending approvals. * edit role assignments: saving them resolves the pending approvals.
*/ */
public function getAssignedRoles(): array public function getAssignedRoles(): array
@@ -283,12 +283,12 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
} }
/** /**
* Super admin is an elevation of ROLE_ADMIN, never a standalone grant. * Super admin is an elevation of ROLE_TEAM_ADMIN, never a standalone grant.
*/ */
#[Assert\Callback] #[Assert\Callback]
public function validateSuperAdmin(ExecutionContextInterface $context): void public function validateSuperAdmin(ExecutionContextInterface $context): void
{ {
if (true === $this->superAdmin && false === in_array('ROLE_ADMIN', $this->roles, true)) { if (true === $this->superAdmin && false === in_array('ROLE_TEAM_ADMIN', $this->roles, true)) {
$context $context
->buildViolation('Nur Admins können zu Superadmins ernannt werden.') ->buildViolation('Nur Admins können zu Superadmins ernannt werden.')
->atPath('superAdmin') ->atPath('superAdmin')
@@ -311,7 +311,7 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
public function getDefaultRoute(): string public function getDefaultRoute(): string
{ {
if ($this->hasRole('ROLE_ADMIN')) { if ($this->hasRole('ROLE_TEAM_ADMIN')) {
return 'app_admin_index'; return 'app_admin_index';
} elseif ($this->hasRole('ROLE_MANAGER')) { } elseif ($this->hasRole('ROLE_MANAGER')) {
return 'app_manager_index'; return 'app_manager_index';
+2 -2
View File
@@ -67,7 +67,7 @@ class UserType extends AbstractType
}); });
// Super admin is an elevation of an existing role, never a grant of its own, so the // Super admin is an elevation of an existing role, never a grant of its own, so the
// field exists only for somebody who already holds ROLE_ADMIN - offering it to // field exists only for somebody who already holds ROLE_TEAM_ADMIN - offering it to
// anyone else would only produce the violation from User::validateSuperAdmin(). // anyone else would only produce the violation from User::validateSuperAdmin().
// A flag already set without the role is the exception: it has to stay editable, or // A flag already set without the role is the exception: it has to stay editable, or
// that user could not be saved at all until BusPro claims them an admin again. // that user could not be saved at all until BusPro claims them an admin again.
@@ -78,7 +78,7 @@ class UserType extends AbstractType
return; return;
} }
if (false === in_array('ROLE_ADMIN', $user->getAssignedRoles(), true) && false === $user->isSuperAdmin()) { if (false === in_array('ROLE_TEAM_ADMIN', $user->getAssignedRoles(), true) && false === $user->isSuperAdmin()) {
return; return;
} }
+3 -3
View File
@@ -19,7 +19,7 @@ abstract class AbstractMenuBuilder
* Note that "app_admin_" does not match the shared "app_administrative_" routes. * Note that "app_admin_" does not match the shared "app_administrative_" routes.
*/ */
protected const AREA_ROUTE_PREFIXES = [ protected const AREA_ROUTE_PREFIXES = [
'app_admin_' => 'ROLE_ADMIN', 'app_admin_' => 'ROLE_TEAM_ADMIN',
'app_manager_' => 'ROLE_MANAGER', 'app_manager_' => 'ROLE_MANAGER',
'app_house_manager_' => 'ROLE_HOUSE_MANAGER', 'app_house_manager_' => 'ROLE_HOUSE_MANAGER',
'app_teamer_' => 'ROLE_TEAMER', 'app_teamer_' => 'ROLE_TEAMER',
@@ -29,7 +29,7 @@ abstract class AbstractMenuBuilder
* Priority order of the area roles, must stay in sync with User::getDefaultRoute(). * Priority order of the area roles, must stay in sync with User::getDefaultRoute().
*/ */
protected const ROLE_PRIORITY = [ protected const ROLE_PRIORITY = [
'ROLE_ADMIN', 'ROLE_TEAM_ADMIN',
'ROLE_MANAGER', 'ROLE_MANAGER',
'ROLE_HOUSE_MANAGER', 'ROLE_HOUSE_MANAGER',
'ROLE_TEAMER', 'ROLE_TEAMER',
@@ -140,7 +140,7 @@ abstract class AbstractMenuBuilder
protected function addAdminItem(ItemInterface $menu): void protected function addAdminItem(ItemInterface $menu): void
{ {
if ($this->security->isGranted('ROLE_ADMIN')) { if ($this->security->isGranted('ROLE_TEAM_ADMIN')) {
$this->addDivider($menu); $this->addDivider($menu);
$menu->addChild('zum Adminbereich', [ $menu->addChild('zum Adminbereich', [
'route' => 'app_admin_index', 'route' => 'app_admin_index',
+2 -2
View File
@@ -32,7 +32,7 @@ class MenuBuilder extends AbstractMenuBuilder
public function createMainMenu(array $options): ItemInterface public function createMainMenu(array $options): ItemInterface
{ {
return match ($this->resolveArea()) { return match ($this->resolveArea()) {
'ROLE_ADMIN' => $this->adminMenuBuilder->createMainMenu($options), 'ROLE_TEAM_ADMIN' => $this->adminMenuBuilder->createMainMenu($options),
'ROLE_MANAGER' => $this->managerMenuBuilder->createMainMenu($options), 'ROLE_MANAGER' => $this->managerMenuBuilder->createMainMenu($options),
'ROLE_HOUSE_MANAGER' => $this->houseManagerMenuBuilder->createMainMenu($options), 'ROLE_HOUSE_MANAGER' => $this->houseManagerMenuBuilder->createMainMenu($options),
'ROLE_TEAMER' => $this->teamerMenuBuilder->createMainMenu($options), 'ROLE_TEAMER' => $this->teamerMenuBuilder->createMainMenu($options),
@@ -43,7 +43,7 @@ class MenuBuilder extends AbstractMenuBuilder
public function createTeamerMenu(array $options): ItemInterface public function createTeamerMenu(array $options): ItemInterface
{ {
return match ($this->resolveArea()) { return match ($this->resolveArea()) {
'ROLE_ADMIN' => $this->adminMenuBuilder->createTeamerMenu($options), 'ROLE_TEAM_ADMIN' => $this->adminMenuBuilder->createTeamerMenu($options),
'ROLE_MANAGER' => $this->managerMenuBuilder->createTeamerMenu($options), 'ROLE_MANAGER' => $this->managerMenuBuilder->createTeamerMenu($options),
default => $this->createRootElement(), default => $this->createRootElement(),
}; };
+2 -1
View File
@@ -148,7 +148,8 @@ class AvailabilityRepository extends ServiceEntityRepository
$qb->expr()->eq('availability.dateTo', ':dateTo'), $qb->expr()->eq('availability.dateTo', ':dateTo'),
$qb->expr()->isNull('availability.owner') $qb->expr()->isNull('availability.owner')
)) ))
->setParameters($fields) ->setParameter('dateFrom', $fields['dateFrom'])
->setParameter('dateTo', $fields['dateTo'])
->getQuery() ->getQuery()
->getResult() ->getResult()
; ;
@@ -7,7 +7,7 @@ use App\Entity\User;
abstract class AbstractRequiredTeamerCheck implements RequiredTeamerCheckInterface abstract class AbstractRequiredTeamerCheck implements RequiredTeamerCheckInterface
{ {
private const EXCLUDED_ROLES = [ private const EXCLUDED_ROLES = [
'ROLE_ADMIN', 'ROLE_TEAM_ADMIN',
'ROLE_MANAGER', 'ROLE_MANAGER',
'ROLE_HOUSE_MANAGER', 'ROLE_HOUSE_MANAGER',
]; ];
+1 -1
View File
@@ -35,7 +35,7 @@ class MyEpAuthenticator extends AbstractAuthenticator
* is dropped rather than stored, so that no role this application assigns a meaning * is dropped rather than stored, so that no role this application assigns a meaning
* to can be set from the outside. * to can be set from the outside.
*/ */
private const ELIGIBLE_ROLES = ['ROLE_ADMIN', 'ROLE_TEAMER', 'ROLE_MANAGER', 'ROLE_HOUSE_MANAGER']; private const ELIGIBLE_ROLES = ['ROLE_TEAM_ADMIN', 'ROLE_TEAMER', 'ROLE_MANAGER', 'ROLE_HOUSE_MANAGER'];
public function __construct( public function __construct(
private readonly MyEpClient $client, private readonly MyEpClient $client,
+2 -2
View File
@@ -60,7 +60,7 @@ class DispositionVoter extends Voter
static::CONTRACT, static::INVOICE => false === $disposition->isSkipFormalities() static::CONTRACT, static::INVOICE => false === $disposition->isSkipFormalities()
&& ($this->security->isGranted('ROLE_ADMINISTRATIVE') && ($this->security->isGranted('ROLE_ADMINISTRATIVE')
|| $this->assertTeamerAccess($token, $disposition)), || $this->assertTeamerAccess($token, $disposition)),
static::DELETE => $this->security->isGranted('ROLE_ADMIN'), static::DELETE => $this->security->isGranted('ROLE_TEAM_ADMIN'),
static::FEEDBACK => false === $disposition->isSkipFormalities() static::FEEDBACK => false === $disposition->isSkipFormalities()
&& ($this->security->isGranted('ROLE_ADMINISTRATIVE') && ($this->security->isGranted('ROLE_ADMINISTRATIVE')
|| $this->assertHouseManagerAccess($token, $disposition)), || $this->assertHouseManagerAccess($token, $disposition)),
@@ -162,7 +162,7 @@ class DispositionVoter extends Voter
private function assertAdminDocumentUploadAllowed(Disposition $disposition): bool private function assertAdminDocumentUploadAllowed(Disposition $disposition): bool
{ {
if (false === $this->security->isGranted('ROLE_ADMINISTRATIVE') if (false === $this->security->isGranted('ROLE_ADMINISTRATIVE')
&& false === $this->security->isGranted('ROLE_ADMIN')) { && false === $this->security->isGranted('ROLE_TEAM_ADMIN')) {
return false; return false;
} }
+1 -1
View File
@@ -24,7 +24,7 @@ class FeedbackVoter extends Voter
/** @var Feedback $feedback */ /** @var Feedback $feedback */
$feedback = $subject; $feedback = $subject;
if (in_array('ROLE_ADMIN', $token->getRoleNames())) { if (in_array('ROLE_TEAM_ADMIN', $token->getRoleNames())) {
return true; return true;
} }
+1 -1
View File
@@ -52,7 +52,7 @@ class ImpersonationVoter extends Voter
} }
// Admin is the only role allowed to impersonate // Admin is the only role allowed to impersonate
if (false === $this->security->isGranted('ROLE_ADMIN')) { if (false === $this->security->isGranted('ROLE_TEAM_ADMIN')) {
return false; return false;
} }
+10 -7
View File
@@ -11,7 +11,12 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
abstract class AbstractFilterHandler abstract class AbstractFilterHandler
{ {
protected string $namespace = 'filter:default'; protected string $namespace = 'filter:default';
protected string $modelClass = AbstractFilterDto::class;
/**
* The DTO a subclass builds. No default: AbstractFilterDto is abstract, so any value here
* would only fatal on instantiation - every handler declares its own.
*/
protected string $modelClass;
public function __construct( public function __construct(
protected readonly RequestStack $requestStack, protected readonly RequestStack $requestStack,
@@ -29,6 +34,8 @@ abstract class AbstractFilterHandler
public function setModelClass(string $modelClass): static public function setModelClass(string $modelClass): static
{ {
$this->modelClass = $modelClass; $this->modelClass = $modelClass;
return $this;
} }
public function handleRequest(FormInterface $form): AbstractFilterDto public function handleRequest(FormInterface $form): AbstractFilterDto
@@ -50,13 +57,9 @@ abstract class AbstractFilterHandler
$this->getSession()->remove($this->namespace); $this->getSession()->remove($this->namespace);
} }
public function getFilterSettings(): AbstractFilterDto abstract public function getFilterSettings(): AbstractFilterDto;
{
}
protected function saveFilterSettings(AbstractFilterDto $filterDto): void abstract protected function saveFilterSettings(AbstractFilterDto $filterDto): void;
{
}
protected function getSession(): SessionInterface protected function getSession(): SessionInterface
{ {
+1 -1
View File
@@ -33,7 +33,7 @@ class AppRuntime implements RuntimeExtensionInterface
$user = $teamer->getUser(); $user = $teamer->getUser();
if (null !== $user) { if (null !== $user) {
if ($user->hasRole('ROLE_ADMIN')) { if ($user->hasRole('ROLE_TEAM_ADMIN')) {
$labelItems[] = 'Admin'; $labelItems[] = 'Admin';
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{{ form_start(form) }} {{ form_start(form) }}
<div class="flex flex-col space-y-4 pb-8"> <div class="flex flex-col space-y-4 pb-8">
{# only present for somebody who already holds ROLE_ADMIN, see UserType #} {# only present for somebody who already holds ROLE_TEAM_ADMIN, see UserType #}
{% if form.superAdmin is defined %} {% if form.superAdmin is defined %}
{{ form_row(form.superAdmin) }} {{ form_row(form.superAdmin) }}
{% endif %} {% endif %}
@@ -9,7 +9,7 @@
<div class="pb-8"> <div class="pb-8">
{% include '_partials/_assignment_info_compact.html.twig' %} {% include '_partials/_assignment_info_compact.html.twig' %}
</div> </div>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<div class="col-span-2"> <div class="col-span-2">
<h2 class="font-bold text-lg pb-4"> <h2 class="font-bold text-lg pb-4">
Bewerbungen Bewerbungen
@@ -52,7 +52,7 @@
<th> <th>
{{ knp_pagination_sortable(pagination, 'Busbegleitung', 'assignment.pickup') }} {{ knp_pagination_sortable(pagination, 'Busbegleitung', 'assignment.pickup') }}
</th> </th>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<th> <th>
Bewerbungen Bewerbungen
</th> </th>
@@ -131,7 +131,7 @@
- -
{% endif %} {% endif %}
</td> </td>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<td> <td>
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}" class="flex items-center space-x-2"> <a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}" class="flex items-center space-x-2">
<span>{{ assignment.applications|length }}</span> <span>{{ assignment.applications|length }}</span>
@@ -102,7 +102,7 @@
{{ knp_pagination_render(pagination) }} {{ knp_pagination_render(pagination) }}
</div> </div>
</div> </div>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<a href="{{ path('app_admin_feedback_provide') }}" class="btn" title="Feedback erfassen"> <a href="{{ path('app_admin_feedback_provide') }}" class="btn" title="Feedback erfassen">
Neu Neu
</a> </a>
@@ -26,7 +26,7 @@
Reset Reset
</a> </a>
{% endif %} {% endif %}
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<a href="{{ path('app_admin_teamer_mailing', { 'r': return_url() }) }}" <a href="{{ path('app_admin_teamer_mailing', { 'r': return_url() }) }}"
class="btn btn--small" class="btn btn--small"
title="Mail an die gefilterte Liste schreiben"> title="Mail an die gefilterte Liste schreiben">
@@ -113,7 +113,7 @@
</button> </button>
{% endif %} {% endif %}
{% if teamer.deleted %} {% if teamer.deleted %}
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<button type="button" <button type="button"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
@@ -125,7 +125,7 @@
</button> </button>
{% endif %} {% endif %}
{% elseif teamer.user is not null and teamer.user.disabled %} {% elseif teamer.user is not null and teamer.user.disabled %}
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<button type="button" <button type="button"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
@@ -136,7 +136,7 @@
</button> </button>
{% endif %} {% endif %}
{% else %} {% else %}
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<button type="button" <button type="button"
class="text-red-500" class="text-red-500"
role="menuitem" role="menuitem"
@@ -152,7 +152,7 @@
{{ icon('mask') }} {{ icon('mask') }}
</a> </a>
{% endif %} {% endif %}
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<button type="button" <button type="button"
class="text-red-500" class="text-red-500"
role="menuitem" role="menuitem"
@@ -80,7 +80,7 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_TEAM_ADMIN') %}
<h2 class="text-lg font-bold"> <h2 class="text-lg font-bold">
Interne Anmerkungen Interne Anmerkungen
</h2> </h2>
+61 -1
View File
@@ -10,6 +10,7 @@ use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\ProfileResponse; use App\BusProNet\Model\ProfileResponse;
use App\BusProNet\Model\ProfileUpdateResponse; use App\BusProNet\Model\ProfileUpdateResponse;
use App\BusProNet\ResponseParser; use App\BusProNet\ResponseParser;
use App\BusProNet\ResponseParserException;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ResponseParserTest extends TestCase class ResponseParserTest extends TestCase
@@ -178,6 +179,65 @@ class ResponseParserTest extends TestCase
$this->assertEquals('Gaststätte', $hotel->getType()); $this->assertEquals('Gaststätte', $hotel->getType());
} }
/**
* A broken response used to collapse into one opaque "Unable to parse XML response",
* which is what made the production import failure undiagnosable. Each shape must now
* name itself.
*/
public function testParseEmptyResponse(): void
{
$parser = $this->getParserInstance();
$this->expectException(ResponseParserException::class);
$this->expectExceptionMessage('empty response');
$parser->parseXmlString(ApiClient::TYPE_BASE_DATA_PICKUPS, '');
}
public function testParseTruncatedResponse(): void
{
$content = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="STAMMZUSTIEGE"></satz><zustieg id="1"';
$parser = $this->getParserInstance();
try {
$parser->parseXmlString(ApiClient::TYPE_BASE_DATA_PICKUPS, $content);
$this->fail('Expected a ResponseParserException');
} catch (ResponseParserException $e) {
$this->assertStringContainsString('('.strlen($content).' bytes)', $e->getMessage());
$this->assertStringContainsString('line 1', $e->getMessage());
}
}
public function testParseWellFormedResponseOfAnUnknownType(): void
{
$content = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="STAMMIRGENDWAS"></satz></ergebnis>';
$parser = $this->getParserInstance();
$this->expectException(ResponseParserException::class);
$this->expectExceptionMessage('Unrecognised BusProNet response type "STAMMIRGENDWAS"');
$parser->parseXmlString(ApiClient::TYPE_BASE_DATA_PICKUPS, $content);
}
public function testParsingDoesNotLeakLibxmlErrorState(): void
{
$previous = libxml_use_internal_errors(false);
try {
try {
$this->getParserInstance()->parseXmlString(ApiClient::TYPE_BASE_DATA_PICKUPS, '<ergebnis');
} catch (ResponseParserException) {
}
$this->assertFalse(libxml_use_internal_errors(false));
$this->assertSame([], libxml_get_errors());
} finally {
libxml_use_internal_errors($previous);
}
}
private function loadFixture(string $filename): string private function loadFixture(string $filename): string
{ {
return file_get_contents(__DIR__.'/../Resources/'.$filename); return file_get_contents(__DIR__.'/../Resources/'.$filename);
@@ -186,7 +246,7 @@ class ResponseParserTest extends TestCase
private function getParserInstance(): ResponseParser private function getParserInstance(): ResponseParser
{ {
return new ResponseParser([ return new ResponseParser([
'bpn_crm_id_admin' => 1292, 'bpn_crm_id_team_admin' => 1292,
'bpn_crm_id_manager' => 1293, 'bpn_crm_id_manager' => 1293,
'bpn_crm_id_teamer' => 1070, 'bpn_crm_id_teamer' => 1070,
// Deliberately a small excerpt of the configured map: the fixtures only carry // Deliberately a small excerpt of the configured map: the fixtures only carry
+35 -35
View File
@@ -44,7 +44,7 @@ class UserDataHandlerTest extends TestCase
{ {
yield 'admin only yields the pending marker' => [ yield 'admin only yields the pending marker' => [
(new CrmAttributesResponse())->setAdmin(true), (new CrmAttributesResponse())->setAdmin(true),
[User::PENDING_ROLES['ROLE_ADMIN']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN']],
]; ];
yield 'manager only yields the pending marker' => [ yield 'manager only yields the pending marker' => [
@@ -64,12 +64,12 @@ class UserDataHandlerTest extends TestCase
yield 'admin and teamer' => [ yield 'admin and teamer' => [
(new CrmAttributesResponse())->setAdmin(true)->setTeamer(true), (new CrmAttributesResponse())->setAdmin(true)->setTeamer(true),
[User::PENDING_ROLES['ROLE_ADMIN'], 'ROLE_TEAMER'], [User::PENDING_ROLES['ROLE_TEAM_ADMIN'], 'ROLE_TEAMER'],
]; ];
yield 'admin and manager yield both markers' => [ yield 'admin and manager yield both markers' => [
(new CrmAttributesResponse())->setAdmin(true)->setManager(true), (new CrmAttributesResponse())->setAdmin(true)->setManager(true),
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']],
]; ];
yield 'manager and house manager yield both markers, the roles stand on their own' => [ yield 'manager and house manager yield both markers, the roles stand on their own' => [
@@ -100,9 +100,9 @@ class UserDataHandlerTest extends TestCase
public static function toPendingRolesProvider(): iterable public static function toPendingRolesProvider(): iterable
{ {
yield 'admin' => [ yield 'admin' => [
['ROLE_ADMIN'], ['ROLE_TEAM_ADMIN'],
(new CrmAttributesResponse())->setAdmin(true), (new CrmAttributesResponse())->setAdmin(true),
[User::PENDING_ROLES['ROLE_ADMIN']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN']],
]; ];
yield 'manager and house manager are marked independently' => [ yield 'manager and house manager are marked independently' => [
@@ -118,9 +118,9 @@ class UserDataHandlerTest extends TestCase
]; ];
yield 'admin and house manager' => [ yield 'admin and house manager' => [
['ROLE_ADMIN', 'ROLE_HOUSE_MANAGER'], ['ROLE_TEAM_ADMIN', 'ROLE_HOUSE_MANAGER'],
(new CrmAttributesResponse())->setAdmin(true)->setHouseManager(true), (new CrmAttributesResponse())->setAdmin(true)->setHouseManager(true),
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
]; ];
yield 'teamer has no marker' => [ yield 'teamer has no marker' => [
@@ -144,7 +144,7 @@ class UserDataHandlerTest extends TestCase
->setEmail('[email protected]') ->setEmail('[email protected]')
->setBusProAddressId(1) ->setBusProAddressId(1)
->setBusProPersonId(2) ->setBusProPersonId(2)
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setHotelCodes(['XYZ']) ->setHotelCodes(['XYZ'])
; ;
@@ -172,7 +172,7 @@ class UserDataHandlerTest extends TestCase
$profileResponse, $profileResponse,
true, true,
['team' => ['selected' => true]], ['team' => ['selected' => true]],
['ROLE_ADMIN', 'ROLE_TEAMER'], ['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'],
['DKS'], ['DKS'],
); );
@@ -182,7 +182,7 @@ class UserDataHandlerTest extends TestCase
// the CRM leads: the still claimed role survives, the houses are replaced by its own // the CRM leads: the still claimed role survives, the houses are replaced by its own
$this->assertSame(['DKS'], $user->getHotelCodes()); $this->assertSame(['DKS'], $user->getHotelCodes());
$this->assertTrue($user->hasRole('ROLE_ADMIN')); $this->assertTrue($user->hasRole('ROLE_TEAM_ADMIN'));
$this->assertSame('New', $teamer->getFirstName()); $this->assertSame('New', $teamer->getFirstName());
$this->assertSame('Lastname', $teamer->getLastName()); $this->assertSame('Lastname', $teamer->getLastName());
@@ -248,9 +248,9 @@ class UserDataHandlerTest extends TestCase
yield 'a claim beyond the approved role stays pending' => [ yield 'a claim beyond the approved role stays pending' => [
['ROLE_MANAGER'], ['ROLE_MANAGER'],
['ROLE_ADMIN', 'ROLE_MANAGER'], ['ROLE_TEAM_ADMIN', 'ROLE_MANAGER'],
['ROLE_MANAGER'], ['ROLE_MANAGER'],
[User::PENDING_ROLES['ROLE_ADMIN']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN']],
]; ];
yield 'the claimed role changes' => [ yield 'the claimed role changes' => [
@@ -261,7 +261,7 @@ class UserDataHandlerTest extends TestCase
]; ];
yield 'a granted role is revoked once the CRM stops claiming it' => [ yield 'a granted role is revoked once the CRM stops claiming it' => [
['ROLE_ADMIN', 'ROLE_TEAMER'], ['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'],
['ROLE_TEAMER'], ['ROLE_TEAMER'],
['ROLE_TEAMER'], ['ROLE_TEAMER'],
[], [],
@@ -282,14 +282,14 @@ class UserDataHandlerTest extends TestCase
->setFirstName('First') ->setFirstName('First')
->setLastName('Last') ->setLastName('Last')
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles([User::PENDING_ROLES['ROLE_ADMIN']]) ->setRoles([User::PENDING_ROLES['ROLE_TEAM_ADMIN']])
; ;
$handler = new UserDataHandler($this->entityManager, $this->logger); $handler = new UserDataHandler($this->entityManager, $this->logger);
$handler->updateLocalUser($user, $this->createProfileResponse(), true, [], ['ROLE_ADMIN', 'ROLE_TEAMER']); $handler->updateLocalUser($user, $this->createProfileResponse(), true, [], ['ROLE_TEAM_ADMIN', 'ROLE_TEAMER']);
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertSame([User::PENDING_ROLES['ROLE_ADMIN']], $user->getPendingRoles()); $this->assertSame([User::PENDING_ROLES['ROLE_TEAM_ADMIN']], $user->getPendingRoles());
} }
/** /**
@@ -302,13 +302,13 @@ class UserDataHandlerTest extends TestCase
->setFirstName('First') ->setFirstName('First')
->setLastName('Last') ->setLastName('Last')
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER']) ->setRoles(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'])
; ;
$handler = new UserDataHandler($this->entityManager, $this->logger); $handler = new UserDataHandler($this->entityManager, $this->logger);
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_ADMIN']); $handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_TEAM_ADMIN']);
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAM_ADMIN'], $user->getAssignedRoles());
} }
public function testUpdateLocalUserGrantsTheTeamerRoleOnlyOnce(): void public function testUpdateLocalUserGrantsTheTeamerRoleOnlyOnce(): void
@@ -327,8 +327,8 @@ class UserDataHandlerTest extends TestCase
} }
/** /**
* ROLE_SUPER_ADMIN is not a stored role but a flag getRoles() turns into one, so * ROLE_TEAM_SUPER_ADMIN is not a stored role but a flag getRoles() turns into one, so
* revoking ROLE_ADMIN has to take it down explicitly - otherwise the highest privilege * revoking ROLE_TEAM_ADMIN has to take it down explicitly - otherwise the highest privilege
* in the application would outlive the role it depends on. * in the application would outlive the role it depends on.
*/ */
public function testUpdateLocalUserTakesTheSuperAdminFlagDownWithRoleAdmin(): void public function testUpdateLocalUserTakesTheSuperAdminFlagDownWithRoleAdmin(): void
@@ -337,7 +337,7 @@ class UserDataHandlerTest extends TestCase
->setFirstName('First') ->setFirstName('First')
->setLastName('Last') ->setLastName('Last')
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER']) ->setRoles(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'])
->setSuperAdmin(true) ->setSuperAdmin(true)
; ;
@@ -346,7 +346,7 @@ class UserDataHandlerTest extends TestCase
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertFalse($user->isSuperAdmin()); $this->assertFalse($user->isSuperAdmin());
$this->assertFalse($user->hasRole('ROLE_SUPER_ADMIN')); $this->assertFalse($user->hasRole('ROLE_TEAM_SUPER_ADMIN'));
} }
public function testUpdateLocalUserKeepsTheSuperAdminFlagOfAStillClaimedAdmin(): void public function testUpdateLocalUserKeepsTheSuperAdminFlagOfAStillClaimedAdmin(): void
@@ -355,15 +355,15 @@ class UserDataHandlerTest extends TestCase
->setFirstName('First') ->setFirstName('First')
->setLastName('Last') ->setLastName('Last')
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER']) ->setRoles(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'])
->setSuperAdmin(true) ->setSuperAdmin(true)
; ;
$handler = new UserDataHandler($this->entityManager, $this->logger); $handler = new UserDataHandler($this->entityManager, $this->logger);
$handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_ADMIN']); $handler->updateLocalUser($user, $this->createProfileResponse(), false, [], ['ROLE_TEAM_ADMIN']);
$this->assertTrue($user->isSuperAdmin()); $this->assertTrue($user->isSuperAdmin());
$this->assertTrue($user->hasRole('ROLE_SUPER_ADMIN')); $this->assertTrue($user->hasRole('ROLE_TEAM_SUPER_ADMIN'));
} }
public function testUpdateLocalUserReplacesTheHotelCodesWithTheOnesTheCrmReports(): void public function testUpdateLocalUserReplacesTheHotelCodesWithTheOnesTheCrmReports(): void
@@ -394,7 +394,7 @@ class UserDataHandlerTest extends TestCase
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles([ ->setRoles([
'ROLE_TEAMER', 'ROLE_TEAMER',
User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_TEAM_ADMIN'],
User::PENDING_ROLES['ROLE_HOUSE_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER'],
]) ])
; ;
@@ -403,8 +403,8 @@ class UserDataHandlerTest extends TestCase
$handler = new UserDataHandler($this->entityManager, $this->logger); $handler = new UserDataHandler($this->entityManager, $this->logger);
$this->assertTrue($handler->approveRole($user, 'ROLE_ADMIN')); $this->assertTrue($handler->approveRole($user, 'ROLE_TEAM_ADMIN'));
$this->assertSame(['ROLE_TEAMER', 'ROLE_ADMIN'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER', 'ROLE_TEAM_ADMIN'], $user->getAssignedRoles());
// the other nomination is untouched: one decision at a time // the other nomination is untouched: one decision at a time
$this->assertSame([User::PENDING_ROLES['ROLE_HOUSE_MANAGER']], $user->getPendingRoles()); $this->assertSame([User::PENDING_ROLES['ROLE_HOUSE_MANAGER']], $user->getPendingRoles());
@@ -437,13 +437,13 @@ class UserDataHandlerTest extends TestCase
{ {
yield 'the CRM never claimed it' => [['ROLE_TEAMER'], 'ROLE_MANAGER']; yield 'the CRM never claimed it' => [['ROLE_TEAMER'], 'ROLE_MANAGER'];
yield 'a different role is nominated' => [[User::PENDING_ROLES['ROLE_MANAGER']], 'ROLE_ADMIN']; yield 'a different role is nominated' => [[User::PENDING_ROLES['ROLE_MANAGER']], 'ROLE_TEAM_ADMIN'];
yield 'already granted, so there is no marker left' => [['ROLE_ADMIN'], 'ROLE_ADMIN']; yield 'already granted, so there is no marker left' => [['ROLE_TEAM_ADMIN'], 'ROLE_TEAM_ADMIN'];
yield 'teamer has no nomination to approve' => [['ROLE_TEAMER'], 'ROLE_TEAMER']; yield 'teamer has no nomination to approve' => [['ROLE_TEAMER'], 'ROLE_TEAMER'];
yield 'not a role at all' => [[User::PENDING_ROLES['ROLE_ADMIN']], 'ROLE_SUPER_ADMIN']; yield 'not a role at all' => [[User::PENDING_ROLES['ROLE_TEAM_ADMIN']], 'ROLE_TEAM_SUPER_ADMIN'];
} }
public function testDisableForRevokedCrmRolesBlocksTheUserAndDropsThePendingMarkers(): void public function testDisableForRevokedCrmRolesBlocksTheUserAndDropsThePendingMarkers(): void
@@ -452,7 +452,7 @@ class UserDataHandlerTest extends TestCase
->setFirstName('First') ->setFirstName('First')
->setLastName('Last') ->setLastName('Last')
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles(['ROLE_ADMIN', User::PENDING_ROLES['ROLE_MANAGER']]) ->setRoles(['ROLE_TEAM_ADMIN', User::PENDING_ROLES['ROLE_MANAGER']])
; ;
$this->entityManager $this->entityManager
@@ -468,7 +468,7 @@ class UserDataHandlerTest extends TestCase
$this->assertSame('Automatisch gesperrt: keine Rollen in BusPro.', $user->getDisabledReasonInternal()); $this->assertSame('Automatisch gesperrt: keine Rollen in BusPro.', $user->getDisabledReasonInternal());
// the granted role is kept so the user stays reviewable, the marker is not // the granted role is kept so the user stays reviewable, the marker is not
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAM_ADMIN'], $user->getAssignedRoles());
$this->assertSame([], $user->getPendingRoles()); $this->assertSame([], $user->getPendingRoles());
} }
@@ -480,7 +480,7 @@ class UserDataHandlerTest extends TestCase
->setFirstName('First') ->setFirstName('First')
->setLastName('Last') ->setLastName('Last')
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setDisabledAt($disabledAt) ->setDisabledAt($disabledAt)
->setDisabledReason('Wegen Fehlverhaltens gesperrt.') ->setDisabledReason('Wegen Fehlverhaltens gesperrt.')
->setDisabledReasonInternal('Siehe Vorgang 4711.') ->setDisabledReasonInternal('Siehe Vorgang 4711.')
+7 -7
View File
@@ -30,18 +30,18 @@ class UserTest extends TestCase
public function testPendingMarkersAreLabelledButNotAssignable(): void public function testPendingMarkersAreLabelledButNotAssignable(): void
{ {
$user = (new User())->setRoles([ $user = (new User())->setRoles([
User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_TEAM_ADMIN'],
User::PENDING_ROLES['ROLE_HOUSE_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER'],
'ROLE_TEAMER', 'ROLE_TEAMER',
]); ]);
$this->assertSame( $this->assertSame(
['Admin (nicht freigeschaltet)', 'Hausleitung (nicht freigeschaltet)', 'Teamer'], ['Team Admin (nicht freigeschaltet)', 'Hausleitung (nicht freigeschaltet)', 'Teamer'],
$user->getRolesLabels(), $user->getRolesLabels(),
); );
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertSame( $this->assertSame(
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER']],
$user->getPendingRoles(), $user->getPendingRoles(),
); );
} }
@@ -62,7 +62,7 @@ class UserTest extends TestCase
public function testSuperAdminWithRoleAdminIsValid(): void public function testSuperAdminWithRoleAdminIsValid(): void
{ {
$user = (new User()) $user = (new User())
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setSuperAdmin(true) ->setSuperAdmin(true)
; ;
@@ -170,19 +170,19 @@ class UserTest extends TestCase
{ {
$user = (new User())->setRoles([ $user = (new User())->setRoles([
'ROLE_TEAMER', 'ROLE_TEAMER',
User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_TEAM_ADMIN'],
User::PENDING_ROLES['ROLE_HOUSE_MANAGER'], User::PENDING_ROLES['ROLE_HOUSE_MANAGER'],
]); ]);
$this->assertSame( $this->assertSame(
['ROLE_ADMIN' => 'Admin', 'ROLE_HOUSE_MANAGER' => 'Hausleitung'], ['ROLE_TEAM_ADMIN' => 'Team Admin', 'ROLE_HOUSE_MANAGER' => 'Hausleitung'],
$user->getNominatedRoles(), $user->getNominatedRoles(),
); );
} }
public function testAGrantedRoleIsNotNominated(): void public function testAGrantedRoleIsNotNominated(): void
{ {
$user = (new User())->setRoles(['ROLE_ADMIN']); $user = (new User())->setRoles(['ROLE_TEAM_ADMIN']);
$this->assertSame([], $user->getNominatedRoles()); $this->assertSame([], $user->getNominatedRoles());
} }
+10 -10
View File
@@ -19,7 +19,7 @@ class UserTypeTest extends KernelTestCase
public function testRolesAndHotelCodesAreNotFields(): void public function testRolesAndHotelCodesAreNotFields(): void
{ {
$user = (new User()) $user = (new User())
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setHotelCodes(['DKS']) ->setHotelCodes(['DKS'])
; ;
@@ -39,7 +39,7 @@ class UserTypeTest extends KernelTestCase
$form = $this->createForm($user); $form = $this->createForm($user);
$form->submit([ $form->submit([
'roles' => ['ROLE_ADMIN'], 'roles' => ['ROLE_TEAM_ADMIN'],
'hotelCodes' => ['DKS'], 'hotelCodes' => ['DKS'],
'disabled' => null, 'disabled' => null,
]); ]);
@@ -49,14 +49,14 @@ class UserTypeTest extends KernelTestCase
} }
/** /**
* Super admin is an elevation of ROLE_ADMIN, so it is not on offer for anybody else - * Super admin is an elevation of ROLE_TEAM_ADMIN, so it is not on offer for anybody else -
* offering it would only ever produce the violation from User::validateSuperAdmin(). * offering it would only ever produce the violation from User::validateSuperAdmin().
*/ */
public function testSuperAdminIsOnlyOfferedToAnAdmin(): void public function testSuperAdminIsOnlyOfferedToAnAdmin(): void
{ {
$this->assertTrue($this->createForm((new User())->setRoles(['ROLE_ADMIN']))->has('superAdmin')); $this->assertTrue($this->createForm((new User())->setRoles(['ROLE_TEAM_ADMIN']))->has('superAdmin'));
$this->assertFalse($this->createForm((new User())->setRoles(['ROLE_MANAGER']))->has('superAdmin')); $this->assertFalse($this->createForm((new User())->setRoles(['ROLE_MANAGER']))->has('superAdmin'));
$this->assertFalse($this->createForm((new User())->setRoles([User::PENDING_ROLES['ROLE_ADMIN']]))->has('superAdmin')); $this->assertFalse($this->createForm((new User())->setRoles([User::PENDING_ROLES['ROLE_TEAM_ADMIN']]))->has('superAdmin'));
} }
/** /**
@@ -84,7 +84,7 @@ class UserTypeTest extends KernelTestCase
public function testSuperAdminIsAppointed(): void public function testSuperAdminIsAppointed(): void
{ {
$user = (new User())->setRoles(['ROLE_ADMIN']); $user = (new User())->setRoles(['ROLE_TEAM_ADMIN']);
$form = $this->createForm($user); $form = $this->createForm($user);
$form->submit([ $form->submit([
@@ -98,7 +98,7 @@ class UserTypeTest extends KernelTestCase
public function testSubmitBlocksTheAccountWithAReason(): void public function testSubmitBlocksTheAccountWithAReason(): void
{ {
$user = (new User())->setRoles(['ROLE_ADMIN']); $user = (new User())->setRoles(['ROLE_TEAM_ADMIN']);
$form = $this->createForm($user); $form = $this->createForm($user);
$form->submit([ $form->submit([
@@ -117,7 +117,7 @@ class UserTypeTest extends KernelTestCase
public function testSubmitUnblocksTheAccountAndClearsTheReasons(): void public function testSubmitUnblocksTheAccountAndClearsTheReasons(): void
{ {
$user = (new User()) $user = (new User())
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setDisabledAt(new \DateTimeImmutable('2026-01-01 08:00:00')) ->setDisabledAt(new \DateTimeImmutable('2026-01-01 08:00:00'))
->setDisabledReason('Für deinen Account liegt in BusPro keine Berechtigung mehr vor.') ->setDisabledReason('Für deinen Account liegt in BusPro keine Berechtigung mehr vor.')
->setDisabledReasonInternal('Automatisch gesperrt: keine Rollen in BusPro.') ->setDisabledReasonInternal('Automatisch gesperrt: keine Rollen in BusPro.')
@@ -135,13 +135,13 @@ class UserTypeTest extends KernelTestCase
$this->assertFalse($user->isDisabled()); $this->assertFalse($user->isDisabled());
$this->assertNull($user->getDisabledReason()); $this->assertNull($user->getDisabledReason());
$this->assertNull($user->getDisabledReasonInternal()); $this->assertNull($user->getDisabledReasonInternal());
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAM_ADMIN'], $user->getAssignedRoles());
} }
public function testSubmitUnblockingClearsTheReasonsEvenWhenTheirFieldsAreStillFilled(): void public function testSubmitUnblockingClearsTheReasonsEvenWhenTheirFieldsAreStillFilled(): void
{ {
$user = (new User()) $user = (new User())
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setDisabledAt(new \DateTimeImmutable('2026-01-01 08:00:00')) ->setDisabledAt(new \DateTimeImmutable('2026-01-01 08:00:00'))
->setDisabledReason('Für deinen Account liegt in BusPro keine Berechtigung mehr vor.') ->setDisabledReason('Für deinen Account liegt in BusPro keine Berechtigung mehr vor.')
->setDisabledReasonInternal('Automatisch gesperrt: keine Rollen in BusPro.') ->setDisabledReasonInternal('Automatisch gesperrt: keine Rollen in BusPro.')
+3 -3
View File
@@ -51,7 +51,7 @@ class BpnAuthenticatorTest extends TestCase
public function testExistingUserWithoutClaimedRolesIsBlockedAndReturned(): void public function testExistingUserWithoutClaimedRolesIsBlockedAndReturned(): void
{ {
$user = (new User())->setRoles(['ROLE_ADMIN']); $user = (new User())->setRoles(['ROLE_TEAM_ADMIN']);
$this->stubApiClient($this->createCrmAttributes()); $this->stubApiClient($this->createCrmAttributes());
@@ -76,7 +76,7 @@ class BpnAuthenticatorTest extends TestCase
*/ */
public function testResponseWithoutAttributeGroupsRefusesTheLoginWithoutBlocking(): void public function testResponseWithoutAttributeGroupsRefusesTheLoginWithoutBlocking(): void
{ {
$user = (new User())->setRoles(['ROLE_ADMIN']); $user = (new User())->setRoles(['ROLE_TEAM_ADMIN']);
// an empty payload carries no roles either and must not read as a revocation // an empty payload carries no roles either and must not read as a revocation
$this->stubApiClient(new CrmAttributesResponse()); $this->stubApiClient(new CrmAttributesResponse());
@@ -93,7 +93,7 @@ class BpnAuthenticatorTest extends TestCase
} catch (UserNotFoundException) { } catch (UserNotFoundException) {
} }
$this->assertSame(['ROLE_ADMIN'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAM_ADMIN'], $user->getAssignedRoles());
} }
/** /**
+12 -12
View File
@@ -60,11 +60,11 @@ class MyEpAuthenticatorTest extends TestCase
$this->repository->method('findOneBy')->willReturn(null); $this->repository->method('findOneBy')->willReturn(null);
$this->repository->method('findBy')->willReturn([]); $this->repository->method('findBy')->willReturn([]);
$user = $this->loadUser($this->createUserinfo(['ROLE_ADMIN', 'ROLE_TEAMER'])); $user = $this->loadUser($this->createUserinfo(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER']));
$this->assertSame([User::PENDING_ROLES['ROLE_ADMIN']], $user->getPendingRoles()); $this->assertSame([User::PENDING_ROLES['ROLE_TEAM_ADMIN']], $user->getPendingRoles());
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertNotContains('ROLE_ADMIN', $user->getRoles()); $this->assertNotContains('ROLE_TEAM_ADMIN', $user->getRoles());
$this->assertContains('ROLE_TEAMER', $user->getRoles()); $this->assertContains('ROLE_TEAMER', $user->getRoles());
} }
@@ -118,14 +118,14 @@ class MyEpAuthenticatorTest extends TestCase
$user = (new User())->setEmail('[email protected]')->setRoles(['ROLE_TEAMER']); $user = (new User())->setEmail('[email protected]')->setRoles(['ROLE_TEAMER']);
$this->repository->method('findOneBy')->willReturn($user); $this->repository->method('findOneBy')->willReturn($user);
$this->loadUser($this->createUserinfo(['ROLE_ADMIN', 'ROLE_MANAGER', 'ROLE_TEAMER'])); $this->loadUser($this->createUserinfo(['ROLE_TEAM_ADMIN', 'ROLE_MANAGER', 'ROLE_TEAMER']));
$this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertSame( $this->assertSame(
[User::PENDING_ROLES['ROLE_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']], [User::PENDING_ROLES['ROLE_TEAM_ADMIN'], User::PENDING_ROLES['ROLE_MANAGER']],
$user->getPendingRoles(), $user->getPendingRoles(),
); );
$this->assertNotContains('ROLE_ADMIN', $user->getRoles()); $this->assertNotContains('ROLE_TEAM_ADMIN', $user->getRoles());
} }
/** /**
@@ -133,12 +133,12 @@ class MyEpAuthenticatorTest extends TestCase
*/ */
public function testAnAlreadyGrantedAdministrativeRoleIsKeptWhileStillClaimed(): void public function testAnAlreadyGrantedAdministrativeRoleIsKeptWhileStillClaimed(): void
{ {
$user = (new User())->setEmail('[email protected]')->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER']); $user = (new User())->setEmail('[email protected]')->setRoles(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER']);
$this->repository->method('findOneBy')->willReturn($user); $this->repository->method('findOneBy')->willReturn($user);
$this->loadUser($this->createUserinfo(['ROLE_ADMIN', 'ROLE_TEAMER'])); $this->loadUser($this->createUserinfo(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER']));
$this->assertSame(['ROLE_ADMIN', 'ROLE_TEAMER'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'], $user->getAssignedRoles());
$this->assertSame([], $user->getPendingRoles()); $this->assertSame([], $user->getPendingRoles());
} }
@@ -150,7 +150,7 @@ class MyEpAuthenticatorTest extends TestCase
{ {
$user = (new User()) $user = (new User())
->setEmail('[email protected]') ->setEmail('[email protected]')
->setRoles(['ROLE_ADMIN', 'ROLE_TEAMER']) ->setRoles(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER'])
->setSuperAdmin(true) ->setSuperAdmin(true)
->setHotelCodes(['SSL']) ->setHotelCodes(['SSL'])
; ;
@@ -163,7 +163,7 @@ class MyEpAuthenticatorTest extends TestCase
// the flag would otherwise outlive the role it depends on // the flag would otherwise outlive the role it depends on
$this->assertFalse($user->isSuperAdmin()); $this->assertFalse($user->isSuperAdmin());
$this->assertNotContains('ROLE_SUPER_ADMIN', $user->getRoles()); $this->assertNotContains('ROLE_TEAM_SUPER_ADMIN', $user->getRoles());
$this->assertSame(['HOTEL'], $user->getHotelCodes()); $this->assertSame(['HOTEL'], $user->getHotelCodes());
} }
@@ -191,7 +191,7 @@ class MyEpAuthenticatorTest extends TestCase
$this->repository->method('findOneBy')->willReturn($user); $this->repository->method('findOneBy')->willReturn($user);
$this->entityManager->expects($this->never())->method('flush'); $this->entityManager->expects($this->never())->method('flush');
$userinfo = $this->createUserinfo(['ROLE_ADMIN', 'ROLE_TEAMER']); $userinfo = $this->createUserinfo(['ROLE_TEAM_ADMIN', 'ROLE_TEAMER']);
$userinfo['profile']['hotel_codes'] = ['NEW']; $userinfo['profile']['hotel_codes'] = ['NEW'];
// returned rather than refused, so the UserChecker can explain the deletion // returned rather than refused, so the UserChecker can explain the deletion
@@ -34,7 +34,7 @@ class DriverLicenseRequiredCheckTest extends TestCase
public function mixedRoleProvider(): array public function mixedRoleProvider(): array
{ {
return [ return [
['ROLE_ADMIN'], ['ROLE_TEAM_ADMIN'],
['ROLE_MANAGER'], ['ROLE_MANAGER'],
['ROLE_HOUSE_MANAGER'], ['ROLE_HOUSE_MANAGER'],
]; ];
@@ -33,7 +33,7 @@ class FinancialDataRequiredCheckTest extends TestCase
public function mixedRoleProvider(): array public function mixedRoleProvider(): array
{ {
return [ return [
['ROLE_ADMIN'], ['ROLE_TEAM_ADMIN'],
['ROLE_MANAGER'], ['ROLE_MANAGER'],
['ROLE_HOUSE_MANAGER'], ['ROLE_HOUSE_MANAGER'],
]; ];
@@ -33,7 +33,7 @@ class MealPreferenceRequiredCheckTest extends TestCase
public function mixedRoleProvider(): array public function mixedRoleProvider(): array
{ {
return [ return [
['ROLE_ADMIN'], ['ROLE_TEAM_ADMIN'],
['ROLE_MANAGER'], ['ROLE_MANAGER'],
['ROLE_HOUSE_MANAGER'], ['ROLE_HOUSE_MANAGER'],
]; ];
@@ -43,7 +43,7 @@ class PersonalDataVerificationRequiredCheckTest extends TestCase
public function mixedRoleProvider(): array public function mixedRoleProvider(): array
{ {
return [ return [
['ROLE_ADMIN'], ['ROLE_TEAM_ADMIN'],
['ROLE_MANAGER'], ['ROLE_MANAGER'],
['ROLE_HOUSE_MANAGER'], ['ROLE_HOUSE_MANAGER'],
]; ];
+1 -1
View File
@@ -75,7 +75,7 @@ class UserVoterTest extends TestCase
private function createUser(bool $superAdmin): User private function createUser(bool $superAdmin): User
{ {
return (new User()) return (new User())
->setRoles(['ROLE_ADMIN']) ->setRoles(['ROLE_TEAM_ADMIN'])
->setSuperAdmin($superAdmin) ->setSuperAdmin($superAdmin)
; ;
} }
@@ -97,14 +97,14 @@ class AccountDeletionHandlerTest extends TestCase
$teamer = new Teamer(); $teamer = new Teamer();
$user = (new User()) $user = (new User())
->setTeamer($teamer) ->setTeamer($teamer)
->setRoles(['ROLE_TEAMER', 'ROLE_ADMIN']) ->setRoles(['ROLE_TEAMER', 'ROLE_TEAM_ADMIN'])
; ;
$user->setDisabled(true); $user->setDisabled(true);
$disabledAt = $user->getDisabledAt(); $disabledAt = $user->getDisabledAt();
$this->handler->delete($user, AccountDeletionHandler::SOURCE_ADMIN); $this->handler->delete($user, AccountDeletionHandler::SOURCE_ADMIN);
$this->assertSame(['ROLE_TEAMER', 'ROLE_ADMIN'], $user->getAssignedRoles()); $this->assertSame(['ROLE_TEAMER', 'ROLE_TEAM_ADMIN'], $user->getAssignedRoles());
$this->assertSame($disabledAt, $user->getDisabledAt()); $this->assertSame($disabledAt, $user->getDisabledAt());
} }