Skip to content

Commit

Permalink
Merge pull request #46 from networkteam/neos-8
Browse files Browse the repository at this point in the history
Neos 8 support
  • Loading branch information
ger4003 authored Aug 27, 2024
2 parents 95251c4 + 2a5e704 commit 6930dee
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 37 deletions.
14 changes: 13 additions & 1 deletion Classes/Controller/AuthenticationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Neos\Flow\Mvc\Exception\NoSuchArgumentException;
use Neos\Flow\Mvc\RequestInterface;
use Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController;
use Neos\Flow\Security\Authentication\TokenInterface;
use Neos\Flow\Security\Cryptography\HashService;
use Neos\Flow\Security\Exception\AuthenticationRequiredException;
use Neos\Flow\Security\Exception\InvalidArgumentForHashGenerationException;
Expand Down Expand Up @@ -44,6 +45,12 @@ class AuthenticationController extends AbstractAuthenticationController
*/
protected $hashService;

/**
* @Flow\InjectConfiguration(path="authenticationProviderName")
* @var string
*/
protected $authenticationProviderName;

/**
* @Flow\Inject
* @var PolicyService
Expand All @@ -68,7 +75,12 @@ public function initializeAction()
*/
public function logoutAction()
{
parent::logoutAction();
foreach ($this->authenticationManager->getSecurityContext()->getAuthenticationTokens() as $token) {
// logout only frontend token
if ($token->getAuthenticationProviderName() == $this->authenticationProviderName) {
$token->setAuthenticationStatus(TokenInterface::NO_CREDENTIALS_GIVEN);
}
}

try {
$redirectAfterLogoutUri = $this->hashService->validateAndStripHmac(
Expand Down
14 changes: 12 additions & 2 deletions Classes/Fusion/FlashMessagesImplementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\FlashMessage\FlashMessageService;
use Neos\Fusion\FusionObjects\AbstractFusionObject;
use Psr\Http\Message\ServerRequestInterface;

class FlashMessagesImplementation extends AbstractFusionObject
{
Expand All @@ -28,16 +29,25 @@ public function evaluate()
$actionRequest = ActionRequest::fromHttpRequest($this->getHttpRequest());
$flashMessageContainer = $this->flashMessageService->getFlashMessageContainerForRequest($actionRequest);

return $flashMessageContainer->getMessagesAndFlush($severity);
if ($this->getFlush()) {
return $flashMessageContainer->getMessagesAndFlush($severity);
} else {
return $flashMessageContainer->getMessages($severity);
}
}

public function getSeverity(): ?string
{
return $this->fusionValue('severity');
}

public function getHttpRequest()
public function getHttpRequest(): ServerRequestInterface
{
return $this->fusionValue('httpRequest');
}

public function getFlush(): bool
{
return (bool)$this->fusionValue('flush');
}
}
66 changes: 62 additions & 4 deletions Classes/Service/UserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
***************************************************************/

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\QueryInterface;
use Neos\Flow\Persistence\QueryResultInterface;
use Neos\Flow\Security\AccountFactory;
use Neos\Neos\Domain\Model\User;
Expand All @@ -30,23 +31,80 @@ class UserService extends \Neos\Neos\Domain\Service\UserService
* @param string $authenticationProviderName
* @return QueryResultInterface
*/
public function getUsers(string $authenticationProviderName = null): QueryResultInterface
public function getUsers(string $sortBy = 'accounts.accountIdentifier', string $sortDirection = QueryInterface::ORDER_ASCENDING, string $authenticationProviderName = null): QueryResultInterface
{
return $this->userRepository->findByAuthenticationProviderName($authenticationProviderName ?: $this->defaultAuthenticationProviderName);
return $this->userRepository->findByAuthenticationProviderName(
$authenticationProviderName ?: $this->defaultAuthenticationProviderName,
$sortBy,
$sortDirection
);
}

/**
* @param string $searchTerm
* @param string $authenticationProviderName
* @param string $sortBy
* @param string $sortDirection
* @param string|null $authenticationProviderName
* @return QueryResultInterface
*/
public function searchUsers(string $searchTerm, string $authenticationProviderName = null): QueryResultInterface
public function searchUsers(string $searchTerm, string $sortBy, string $sortDirection, string $authenticationProviderName = null): QueryResultInterface
{
return $this->userRepository->findBySearchTermAndAuthenticationProviderName(
$searchTerm,
$authenticationProviderName ?: $this->defaultAuthenticationProviderName,
$sortBy,
$sortDirection
);
}

/**
* Adds a user whose User object has been created elsewhere. A workspace is NOT created.
*
* This method basically "creates" a user like createUser() would, except that it does not create the User
* object itself. If you need to create the User object elsewhere, for example in your ActionController, make sure
* to call this method for registering the new user instead of adding it to the PartyRepository manually.
*
* @param string $username The username of the user to be created.
* @param string $password Password of the user to be created
* @param User $user The pre-built user object to start with
* @param array $roleIdentifiers A list of role identifiers to assign
* @param string $authenticationProviderName Name of the authentication provider to use. Example: "Neos.Neos:Backend"
* @return User The same user object
* @api
*/
public function addUser($username, $password, User $user, array $roleIdentifiers = null, $authenticationProviderName = null)
{
if ($roleIdentifiers === null) {
$roleIdentifiers = ['Networkteam.Neos.FrontendLogin:FrontendUser'];
}
$roleIdentifiers = $this->normalizeRoleIdentifiers($roleIdentifiers);
$account = $this->accountFactory->createAccountWithPassword(
$username,
$password,
$roleIdentifiers,
$authenticationProviderName ?: $this->defaultAuthenticationProviderName
);
$this->partyService->assignAccountToParty($account, $user);

$this->partyRepository->add($user);
$this->accountRepository->add($account);

$this->emitFrontendUserCreated($user);

return $user;
}


/**
* Signals that a new frontend user, including a new account has been created.
*
* @param User $user The created user
* @return void
* @Flow\Signal
* @api
*/
public function emitFrontendUserCreated(User $user)
{
}
}

6 changes: 4 additions & 2 deletions Classes/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ class UserRepository extends \Neos\Neos\Domain\Repository\UserRepository

/**
* @param string $authenticationProviderName
* @param string $sortBy
* @param string $sortDirection
* @return QueryResultInterface
*/
public function findByAuthenticationProviderName(string $authenticationProviderName): QueryResultInterface
public function findByAuthenticationProviderName(string $authenticationProviderName, string $sortBy = 'accounts.accountIdentifier', string $sortDirection = QueryInterface::ORDER_ASCENDING): QueryResultInterface
{
try {
$query = $this->createQuery();
$query->matching(
$query->equals('accounts.authenticationProviderName', $authenticationProviderName),
);
return $query->setOrderings(['accounts.accountIdentifier' => QueryInterface::ORDER_ASCENDING])->execute();
return $query->setOrderings([$sortBy => $sortDirection])->execute();
} catch (\Neos\Flow\Persistence\Exception\InvalidQueryException $e) {
throw new \RuntimeException($e->getMessage(), 1621946651, $e);
}
Expand Down
3 changes: 1 addition & 2 deletions Configuration/Routes.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
-
name: 'Frontend Login Form'
uriPattern: 'frontend-login(/{@action}).{@format}'
uriPattern: 'frontend-login/{@action}.{@format}'
defaults:
'@package': 'Networkteam.Neos.FrontendLogin'
'@controller': 'Authentication'
'@format': 'html'
'@action': 'authenticate'
appendExceedingArguments: true
2 changes: 1 addition & 1 deletion Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Neos:
providers:
'Neos.Neos:Backend':
requestPatterns:
'Neos.Neos.FrontendLogin:NeosBackend':
'Networkteam.Neos.FrontendLogin:NeosBackend':
pattern: Networkteam\Neos\FrontendLogin\Security\NeosRequestPattern
'Networkteam.Neos.FrontendLogin:Frontend':
provider: PersistedUsernamePasswordProvider
Expand Down
57 changes: 35 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Networkteam.Neos.FrontendLogin

Neos package for frontend login inspired by [Flowpack.Neos.FrontendLogin](https://github.com/Flowpack/Flowpack.Neos.FrontendLogin).
Neos package for frontend login inspired
by [Flowpack.Neos.FrontendLogin](https://github.com/Flowpack/Flowpack.Neos.FrontendLogin).
It provides a mixin for MemberAreaRootPages. The package makes use of the `accessRoles` property of `NodeInterface`.

**Note:** It seems that the current Neos versions `neos/neos:7.*` and `neos/neos:8.*` are compatible with this package
as the deprecated parts are not yet removed. But be aware of breaking changes in the near future.

## Features

* Place a MemberAreaRootPage within your page tree and all pages beneath it including the member area page itself will be protected
* Configure redirect page after login and logout
* Place a MemberAreaRootPage within your page tree and all pages beneath it including the member area page itself will
be protected
* Configure redirect page after login and logout
* Redirect to configured login form when a protected page is requested without valid login.
After successful login you will be redirected to initially requested page (referer).
* Multiple member areas with different access roles are possible
Expand All @@ -23,8 +28,10 @@ Campatibility with `neos/neos` Package

| Neos.Neos | Networkteam.Neos.FrontendLogin |
|-----------|--------------------------------|
| `4.x` | `1.x` |
| `5.x` | `main` branch |
| `4.*` | `1.3.*` |
| `5.*` | `1.4.*` |
| `7.*` | `1.4.*` |
| `8.*` | `1.5.*` |

## Migration

Expand All @@ -47,33 +54,34 @@ This packages makes use of the neos flow security framework. For further details
[documentation of the flow framework](https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/Security.html?highlight=roles#defining-privileges-policies).

Two role definitions are provided:
* **Networkteam.Neos.FrontendLogin:MemberArea** (abstract): Interface for MemberArea roles. It is used within

* **Networkteam.Neos.FrontendLogin:MemberArea** (abstract): Interface for MemberArea roles. It is used within
access role selection of MemberAreaRootPages.
* **Networkteam.Neos.FrontendLogin:FrontendUser**: Concrete access role implementation

You can define your own frontend user roles by adding them to the `Policy.yaml` of your package. Make sure that you add
`Networkteam.Neos.FrontendLogin:MemberArea` as parent role. Otherwise, you won't be able to select the role within
You can define your own frontend user roles by adding them to the `Policy.yaml` of your package. Make sure that you add
`Networkteam.Neos.FrontendLogin:MemberArea` as parent role. Otherwise, you won't be able to select the role within
MemberAreaRootPage node.

When you set access roles on your MemberAreaRootPage via the inspector and apply the changes, these access roles will be set on all
DocumentNodes beneath that MemberAreaRootPage as well. This ensures that all these pages can only be access by users
When you set access roles on your MemberAreaRootPage via the inspector and apply the changes, these access roles will be
set on all
DocumentNodes beneath that MemberAreaRootPage as well. This ensures that all these pages can only be access by users
having one of the selected roles.

## Create Frontend Users

To create a new Frontend User you can use the `neos.neos:user:create` command, e.g.

```bash
./flow user:create --authentication-provider "Networkteam.Neos.FrontendLogin:Frontend" --roles "Networkteam.Neos.FrontendLogin:FrontendUser"
```

or use the user management module inside Neos backend.


## Create member area

The package does not supply a concrete implementation. It does only supply a mixin.
To create a member area you need to define a specific nodeType for MemberAreaRootPages which
To create a member area you need to define a specific nodeType for MemberAreaRootPages which
implements the mixin provided by this package.

### Define your nodeTypes
Expand All @@ -83,6 +91,7 @@ You need to define one nodeType for MemberAreaRootPages.
An example configuration could look as follows:

*Packages/Application/Your.Package/Configuration/NodeTypes.MemberAreaRootPage.yaml*

```yaml
'Your.Package:MemberAreaRootPage':
superTypes:
Expand All @@ -97,6 +106,7 @@ An example configuration could look as follows:
For your defined nodeType you need a suitable fusion object. An example configuration could look as follows:
*Packages/Application/Your.Package/Resources/Private/Fusion/MemberAreaRootPage.fusion*
```fusion
# MemberAreaRootPage
prototype(Your.Package:MemberAreaRootPage) < prototype(Neos.Neos:Page) {
Expand All @@ -108,40 +118,43 @@ prototype(Your.Package:MemberAreaRootPage) < prototype(Neos.Neos:Page) {

### Add pages and login form

Now you can log into Neos backend and create a new **MemberAreaRootPage**. Define which users should have access to this
Now you can log into Neos backend and create a new **MemberAreaRootPage**. Define which users should have access to this
member area by selecting access roles and apply the changes.

Next you need to add a **login form** on a page which is not protected. Do not place the login form within
Next you need to add a **login form** on a page which is not protected. Do not place the login form within
your member area or the MemberAreaRootPage. Otherwise, your users won't be able to access the login form.

Now go back to the previously created MemberAreaRootPage and select the page containing the login form (`Login form page`).
Now go back to the previously created MemberAreaRootPage and select the page containing the login form (
`Login form page`).

Additionally, you can add further pages beneath your MemberAreaRootPage. They will be protected.

## Adding your own MemberArea roles

If you define your own MemberArea roles via `Policy.yaml`, make sure that you add them as `parentRoles` to
`Neos.Neos:AbstractEditor` role definition. Otherwise pages only having your new roles will not be visible in Neos backend.
This could also lead to error during publishing.
`Neos.Neos:AbstractEditor` role definition. Otherwise pages only having your new roles will not be visible in Neos
backend.
This could also lead to error during publishing.

**Policy.yaml**

```yaml
roles:
'Your.Package:UserWithFrontendAccess':
parentRoles: ['Networkteam.Neos.FrontendLogin:MemberArea']
parentRoles: [ 'Networkteam.Neos.FrontendLogin:MemberArea' ]

'Neos.Neos:AbstractEditor':
parentRoles: ['Your.Package:UserWithFrontendAccess']
parentRoles: [ 'Your.Package:UserWithFrontendAccess' ]
```
## Password reset
To give your frontend users the possibility to reset there password you can install and use the package [Networkteam.Neos.PasswordReset](https://github.com/networkteam/Networkteam.Neos.PasswordReset).
To give your frontend users the possibility to reset there password you can install and use the
package [Networkteam.Neos.PasswordReset](https://github.com/networkteam/Networkteam.Neos.PasswordReset).
## Translation
To translate the login error message or login form labels create a xliff file for each language in your package and
To translate the login error message or login form labels create a xliff file for each language in your package and
set properties `original="Main"` and `product-name="Networkteam.Neos.FrontendLogin"` on `<file>` tag. Now you can
override the language keys from the original translation file of `Networkteam.Neos.FrontendLogin` package you like.

Expand Down
1 change: 1 addition & 0 deletions Resources/Private/Fusion/Prototypes/FlashMessages.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ prototype(Networkteam.Neos.FrontendLogin:FlashMessages) {

severity = null
httpRequest = ${request.httpRequest}
flush = true
}
2 changes: 1 addition & 1 deletion Resources/Private/Translations/de/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</trans-unit>
<trans-unit id="loginhandler.inbackend" xml:space="preserve">
<source>Form is disabled in backend mode.</source>
<target>Das Formular is im Backend-Modus deaktiviert.</target>
<target>Das Formular ist im Backend-Modus deaktiviert.</target>
</trans-unit>

<!-- nodeTypes -->
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
}
],
"require": {
"neos/neos": "~5.0",
"neos/neos": "~8.0",
"neos/fusion-afx": "*"
},
"autoload": {
Expand Down Expand Up @@ -106,4 +106,4 @@
"suggest": {
"networkteam/neos-passwordreset": "Allow users to reset their user account password within the frontend."
}
}
}

0 comments on commit 6930dee

Please sign in to comment.