1919use Neos \Flow \Persistence \Exception \IllegalObjectTypeException ;
2020use Neos \Flow \Security \Account ;
2121use Neos \Flow \Security \AccountRepository ;
22+ use Neos \Flow \Security \Policy \PolicyService ;
23+ use Neos \Flow \Security \Policy \Role ;
2224use Neos \Neos \Controller \Module \AbstractModuleController ;
2325use Neos \Neos \Domain \Exception as NeosDomainException ;
2426use Neos \Neos \Domain \Model \User ;
@@ -35,6 +37,12 @@ class ModuleController extends AbstractModuleController
3537 */
3638 protected $ userService ;
3739
40+ /**
41+ * @Flow\Inject
42+ * @var PolicyService
43+ */
44+ protected $ policyService ;
45+
3846 /**
3947 * @Flow\Inject
4048 * @var AccountRepository
@@ -96,14 +104,13 @@ public function showAction(User $user): void
96104 /**
97105 * Renders a form for creating a new user
98106 *
99- * @param User $user
100107 * @return void
101108 */
102- public function newAction (User $ user = null ): void
109+ public function newAction (): void
103110 {
104111 $ this ->view ->assignMultiple ([
105112 'currentUser ' => $ this ->currentUser ,
106- 'user ' => $ user
113+ 'availableRoles ' => $ this -> getFrontendUserRoles ()
107114 ]);
108115 }
109116
@@ -113,24 +120,31 @@ public function newAction(User $user = null): void
113120 * @param string $username The user name (ie. account identifier) of the new user
114121 * @param array $password Expects an array in the format array('<password>', '<password confirmation>')
115122 * @param User $user The user to create
116- * @param \DateTime $expirationDate
123+ * @param \DateTime|null $expirationDate
124+ * @param array $roleIdentifiers Identifiers of roles to assign to account
117125 * @return void
118126 * @Flow\Validate(argumentName="username", type="\Neos\Flow\Validation\Validator\NotEmptyValidator")
119127 * @Flow\Validate(argumentName="username", type="\Neos\Neos\Validation\Validator\UserDoesNotExistValidator")
120128 * @Flow\Validate(argumentName="password", type="\Neos\Neos\Validation\Validator\PasswordValidator", options={ "allowEmpty"=0, "minimum"=1, "maximum"=255 })
121129 */
122- public function createAction ($ username , array $ password , User $ user , ?\DateTime $ expirationDate ): void
130+ public function createAction (string $ username , array $ password , User $ user , ?\DateTime $ expirationDate, array $ roleIdentifiers = [] ): void
123131 {
124- $ user = $ this ->userService ->addUser ($ username , $ password [0 ], $ user , [self ::$ roleIdentifier ], self ::$ authenticationProviderName );
132+ // make sure self::$roleIdentifier is always added
133+ $ roleIdentifiersToSet = array_unique (array_merge ($ roleIdentifiers , [self ::$ roleIdentifier ]));
134+ if ($ this ->onlyFrontendRoles ($ roleIdentifiersToSet )) {
135+ $ user = $ this ->userService ->addUser ($ username , $ password [0 ], $ user , $ roleIdentifiersToSet , self ::$ authenticationProviderName );
125136
126- if ($ expirationDate !== null ) {
127- /** @var Account $account */
128- $ account = $ user ->getAccounts ()->first ();
129- $ expirationDate ->setTime (0 , 0 , 0 );
130- $ account ->setExpirationDate ($ expirationDate );
131- }
137+ if ($ expirationDate !== null ) {
138+ /** @var Account $account */
139+ $ account = $ user ->getAccounts ()->first ();
140+ $ expirationDate ->setTime (0 , 0 );
141+ $ account ->setExpirationDate ($ expirationDate );
142+ }
132143
133- $ this ->addFlashMessage ('The user "%s" has been created. ' , 'User created ' , Message::SEVERITY_OK , [htmlspecialchars ($ username )], 1416225561 );
144+ $ this ->addFlashMessage ('The user "%s" has been created. ' , 'User created ' , Message::SEVERITY_OK , [htmlspecialchars ($ username )], 1416225561 );
145+ } else {
146+ $ this ->throwStatus (403 , 'Not allowed to assign the given roles ' );
147+ }
134148 $ this ->redirect ('index ' );
135149 }
136150
@@ -209,7 +223,8 @@ public function editAccountAction(Account $account): void
209223 $ this ->view ->assignMultiple ([
210224 'account ' => $ account ,
211225 'user ' => $ this ->userService ->getUser ($ account ->getAccountIdentifier (), $ account ->getAuthenticationProviderName ()),
212- 'expirationDate ' => $ account ->getExpirationDate ()
226+ 'expirationDate ' => $ account ->getExpirationDate (),
227+ 'availableRoles ' => $ this ->getFrontendUserRoles ()
213228 ]);
214229 } else {
215230 $ this ->throwStatus (403 , 'Not allowed to edit that account ' );
@@ -220,25 +235,37 @@ public function editAccountAction(Account $account): void
220235 * Update a given account
221236 *
222237 * @param Account $account The account to update
238+ * @param array $roleIdentifiers Identifiers of roles to assign to account
223239 * @param array $password Expects an array in the format array('<password>', '<password confirmation>')
224240 * @Flow\Validate(argumentName="password", type="\Neos\Neos\Validation\Validator\PasswordValidator", options={ "allowEmpty"=1, "minimum"=1, "maximum"=255 })
225241 * @return void
226242 * @throws NeosDomainException
227243 * @throws IllegalObjectTypeException
228244 * @throws UnsupportedRequestTypeException
229245 */
230- public function updateAccountAction (Account $ account , array $ password = []): void
246+ public function updateAccountAction (Account $ account , array $ roleIdentifiers = [], array $ password = []): void
231247 {
232248 if ($ this ->checkAccount ($ account )) {
233- $ user = $ this -> userService -> getUser ( $ account -> getAccountIdentifier (), $ account -> getAuthenticationProviderName ());
234- $ password = array_shift ( $ password );
235- if ( trim (( string ) $ password ) !== '' ) {
236- $ this ->userService -> setUserPassword ( $ user , $ password );
237- }
238- $ this ->accountRepository -> update ( $ account );
249+ // make sure self::$roleIdentifier is always added
250+ $ roleIdentifiersToSet = array_unique ( array_merge ( $ roleIdentifiers , [ self :: $ roleIdentifier ]) );
251+
252+ if ( $ this ->onlyFrontendRoles ( $ roleIdentifiersToSet )) {
253+ // add any non-FE roles from the current roles to keep them unchanged
254+ $ roleIdentifiersToSet = $ this ->addExistingNonFrontendUserRoles ( $ roleIdentifiersToSet , $ account );
239255
240- $ this ->addFlashMessage ('The account has been updated. ' , 'Account updated ' , Message::SEVERITY_OK );
241- $ this ->redirect ('edit ' , null , null , ['user ' => $ user ]);
256+ $ this ->userService ->setRolesForAccount ($ account , $ roleIdentifiersToSet );
257+ $ user = $ this ->userService ->getUser ($ account ->getAccountIdentifier (), $ account ->getAuthenticationProviderName ());
258+ $ password = array_shift ($ password );
259+ if (trim ((string )$ password ) !== '' ) {
260+ $ this ->userService ->setUserPassword ($ user , $ password );
261+ }
262+ $ this ->accountRepository ->update ($ account );
263+
264+ $ this ->addFlashMessage ('The account has been updated. ' , 'Account updated ' );
265+ $ this ->redirect ('edit ' , null , null , ['user ' => $ user ]);
266+ } else {
267+ $ this ->throwStatus (403 , 'Not allowed to assign the given roles ' );
268+ }
242269 } else {
243270 $ this ->throwStatus (403 , 'Not allowed to update that account ' );
244271 }
@@ -293,4 +320,77 @@ protected function checkAccount(Account $account): bool
293320
294321 return false ;
295322 }
323+
324+ /**
325+ * Returns all roles that are (indirect) heirs of self::$roleIdentifier
326+ *
327+ * @return Role[] indexed by role identifier
328+ */
329+ protected function getFrontendUserRoles (): array
330+ {
331+ $ availableRoles = $ this ->policyService ->getRoles ();
332+ return array_filter ($ availableRoles , static function (Role $ role ) {
333+ return $ role ->getIdentifier () === self ::$ roleIdentifier || array_key_exists (self ::$ roleIdentifier , $ role ->getAllParentRoles ());
334+ });
335+ }
336+
337+ /**
338+ * Returns an array with all roles of a user's accounts, including parent roles, the "Everybody" role and the
339+ * "AuthenticatedUser" role, assuming that the user is logged in.
340+ *
341+ * @param Account $account
342+ * @return Role[] indexed by role identifier
343+ */
344+ private function getAllRolesForAccount (Account $ account ): array
345+ {
346+ $ roles = [];
347+ $ accountRoles = $ account ->getRoles ();
348+ foreach ($ accountRoles as $ currentRole ) {
349+ if (!in_array ($ currentRole , $ roles , true )) {
350+ $ roles [$ currentRole ->getIdentifier ()] = $ currentRole ;
351+ }
352+ foreach ($ currentRole ->getAllParentRoles () as $ currentParentRole ) {
353+ if (!in_array ($ currentParentRole , $ roles , true )) {
354+ $ roles [$ currentParentRole ->getIdentifier ()] = $ currentParentRole ;
355+ }
356+ }
357+ }
358+
359+ return $ roles ;
360+ }
361+
362+ /**
363+ * Returns whether it is allowed to add/remove the changed roles.
364+ *
365+ * - Roles not being "FE user roles" can never be set
366+ *
367+ * @param array $roleIdentifiersToSet
368+ * @return bool
369+ */
370+ private function onlyFrontendRoles (array $ roleIdentifiersToSet ): bool
371+ {
372+ $ frontendUserRoleIdentifiers = array_keys ($ this ->getFrontendUserRoles ());
373+
374+ return array_diff ($ roleIdentifiersToSet , $ frontendUserRoleIdentifiers ) === [];
375+ }
376+
377+ /**
378+ * Add any non-FE roles from the current $account roles to $roleIdentifiersToSet
379+ *
380+ * @param array $roleIdentifiersToSet
381+ * @param Account $account
382+ * @return array
383+ */
384+ protected function addExistingNonFrontendUserRoles (array $ roleIdentifiersToSet , Account $ account ): array
385+ {
386+ return array_unique (array_merge (
387+ $ roleIdentifiersToSet ,
388+ array_keys (array_filter (
389+ $ this ->getAllRolesForAccount ($ account ),
390+ static function (Role $ role ) {
391+ return !$ role ->isAbstract () && !($ role ->getIdentifier () === self ::$ roleIdentifier || array_key_exists (self ::$ roleIdentifier , $ role ->getAllParentRoles ()));
392+ }
393+ ))
394+ ));
395+ }
296396}
0 commit comments