Skip to content

Commit

Permalink
Merge pull request #1291 from solverat/login_type
Browse files Browse the repository at this point in the history
Implement Username/Email Login Identifier
  • Loading branch information
dpfaffenbauer authored Feb 19, 2020
2 parents caa277b + 515ad24 commit e30f278
Show file tree
Hide file tree
Showing 28 changed files with 452 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG-2.2.x.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Within 2.2

## 2.2.0
8 changes: 3 additions & 5 deletions docs/03_Development/12_Customers/03_Registration_Service.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# CoreShop Customer Registration Service

CoreShop already implements a registration Service which handles creating a Customer with Addresses. The Registration Service implements the Interface ```CoreShop\Bundle\CoreBundle\Customer``` and CoreShop implements it using the service ```coreshop.customer.registration_service```
CoreShop already implements a registration Service which handles creating a Customer with Addresses.
The Registration Service implements the Interface `CoreShop\Bundle\CoreBundle\Customer` and CoreShop implements it using the service `coreshop.customer.registration_service`.

## Usage

To use the Service, you need to pass a Customer, Address, additional Formdata and if Registration is Guest or Customer.

In our example, we gonna do that from a Controller with a FormTypes
In our example, we gonna do that from a Controller with a FormType.

```php
$customer = $this->getCustomer();
Expand Down
35 changes: 35 additions & 0 deletions docs/03_Development/12_Customers/04_Registration_Types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# CoreShop Customer Registration Types
By default, a customer needs to provide a unique and valid email address to pass a registration.

## Register By Email
> This is the default setting!
To switch to registration by a unique and valid email address, you need set the identifier:

```yaml
core_shop_customer:
login_identifier: 'email'
```
## Register By Username
First, you need to make sure your customer object provides a `username` field.
By default, coreshop **does not** install this field to prevent unnecessary confusion.
To implement the username field, just open your class editor and add a text field called `username` and you're good to go!

To switch to registration by a unique username, you need change the identifier:

```yaml
core_shop_customer:
login_identifier: 'username'
```

## Security

### Form (Frontend)
CoreShop comes with a preinstalled constraint which will tell your customer, if an email address or username - depending on your settings - is valid or not.

### Backend / API
Plus, if you're going to update a customer by API or Backend, coreshop also checks if your customer entity has unique data.

> Note: Both checks only apply to non-guest entities!

41 changes: 41 additions & 0 deletions docs/03_Development/12_Customers/05_Company_Extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# CoreShop Customer Company Extension
The Company Entity allows you to append customers to a given company.
After a customer has been connected to a company by using the 1to1 relation `company`, it's possible to share addresses between company and the self-assigned addresses.

## Access Types
> Note! This is only available if a customer already is connected to a valid company!
### Own Only
If set, the customer can create, edit and delete own addresses and choose them in checkout as well. This is the default behaviour.

### Company Only
If set, the customer can create, edit und delete company addresses and choose them in checkout as well. He's not able to add addresses to himself.

### Own And Company
If set, the customer can create, edit and delete company and private addresses and choose them in checkout as well.

Plus, the `own_and_company` mode allows the customer to define and modify the allocation of the address.
To do so, coreshop renders an additional choice type to the address creation/modification form.

**Note**: If a customer switches the allocation after it has been created, the address also physically gets moved to its desired location.
In this example, the customer changes the allocation from `own` to `company`:

Before:
```yaml
- company A
- addresses
- customer A
- addresses
- address A
```
After:
```yaml
- company A
- addresses
- address A
- customer A
- addresses
```
Read more about this feature [here](https://github.com/coreshop/CoreShop/issues/1266).
5 changes: 3 additions & 2 deletions docs/03_Development/12_Customers/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# CoreShop Customer

This guide should lead you through how CoreShop handles Customer Information.

1. [Create, Read, Update, Delete](./01_CRUD.md)
2. [Customer Context](./02_Context.md)
3. [Registration Service](./03_Registration_Service.md)
3. [Registration Service](./03_Registration_Service.md)
3. [Registration Types](./04_Registration_Types.md)
4. [Company Extension](./05_Company_Extension.md)
13 changes: 13 additions & 0 deletions src/CoreShop/Behat/Context/Transform/CustomerContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,17 @@ public function getCustomerByEmail($email)

return $customer;
}

/**
* @Transform /^customer "([^"]+)"$/
* @Transform /^username "([^"]+)"$/
*/
public function getCustomerByUsername($username)
{
$customer = $this->customerRepository->findCustomerByUsername($username);

Assert::isInstanceOf($customer, CustomerInterface::class);

return $customer;
}
}
13 changes: 11 additions & 2 deletions src/CoreShop/Bundle/CoreBundle/Customer/RegistrationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ final class RegistrationService implements RegistrationServiceInterface
*/
private $addressFolder;

/**
* @var string
*/
private $loginIdentifier;

/**
* @param CustomerRepositoryInterface $customerRepository
* @param ObjectServiceInterface $objectService
Expand All @@ -68,6 +73,7 @@ final class RegistrationService implements RegistrationServiceInterface
* @param string $customerFolder
* @param string $guestFolder
* @param string $addressFolder
* @param string $loginIdentifier
*/
public function __construct(
CustomerRepositoryInterface $customerRepository,
Expand All @@ -76,7 +82,8 @@ public function __construct(
LocaleContextInterface $localeContext,
$customerFolder,
$guestFolder,
$addressFolder
$addressFolder,
$loginIdentifier
) {
$this->customerRepository = $customerRepository;
$this->objectService = $objectService;
Expand All @@ -85,6 +92,7 @@ public function __construct(
$this->customerFolder = $customerFolder;
$this->guestFolder = $guestFolder;
$this->addressFolder = $addressFolder;
$this->loginIdentifier = $loginIdentifier;
}

/**
Expand All @@ -96,7 +104,8 @@ public function registerCustomer(
$formData,
$isGuest = false
) {
$existingCustomer = $this->customerRepository->findCustomerByEmail($customer->getEmail());
$loginIdentifierValue = $this->loginIdentifier === 'email' ? $customer->getEmail() : $customer->getUsername();
$existingCustomer = $this->customerRepository->findUniqueByLoginIdentifier($this->loginIdentifier, $loginIdentifierValue, false);

if ($existingCustomer instanceof CustomerInterface && !$existingCustomer->getIsGuest()) {
throw new CustomerAlreadyExistsException();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
/**
* CoreShop.
*
* This source file is subject to the GNU General Public License version 3 (GPLv3)
* For the full copyright and license information, please view the LICENSE.md and gpl-3.0.txt
* files that are distributed with this source code.
*
* @copyright Copyright (c) 2015-2020 Kamil Wręczycki
* @license https://www.coreshop.org/license GNU General Public License version 3 (GPLv3)
*/

namespace CoreShop\Bundle\CoreBundle\EventListener;

use CoreShop\Bundle\CoreBundle\Customer\CustomerLoginServiceInterface;
use CoreShop\Component\Core\Model\CustomerInterface;
use CoreShop\Component\Customer\Repository\CustomerRepositoryInterface;
use Pimcore\Event\Model\DataObjectEvent;
use Pimcore\Model\Element\ValidationException;

final class CustomerSecurityValidationListener
{
/**
* @var CustomerRepositoryInterface
*/
protected $customerRepository;

/**
* @var string
*/
protected $className;

/**
* @var string
*/
protected $loginIdentifier;

/**
* @param CustomerRepositoryInterface $customerRepository
* @param string $className
* @param string $loginIdentifier
*/
public function __construct(
CustomerRepositoryInterface $customerRepository,
$className,
$loginIdentifier
) {
$this->customerRepository = $customerRepository;
$this->className = $className;
$this->loginIdentifier = $loginIdentifier;
}

/**
* @param DataObjectEvent $event
*
* @throws ValidationException
*/
public function checkCustomerSecurityDataBeforeUpdate(DataObjectEvent $event)
{
$object = $event->getObject();

if (!$object instanceof CustomerInterface) {
return;
}

if ($object->getIsGuest() === true) {
return;
}

$identifierValue = $this->loginIdentifier === 'email' ? $object->getEmail() : $object->getUsername();

$listing = $this->customerRepository->getList();
$listing->setUnpublished(true);
$listing->addConditionParam(sprintf('%s = ?', $this->loginIdentifier), $identifierValue);
$listing->addConditionParam('o_id != ?', $object->getId());

$objects = $listing->getObjects();

if (count($objects) === 0) {
return;
}

throw new ValidationException(sprintf('%s "%s" is already used. Please use another one.', ucfirst($this->loginIdentifier), $identifierValue));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public function up(Schema $schema)
}

if (!$classUpdater->hasField('addressAccessType')) {
$classUpdater->insertFieldBefore('addresses', $addressAccessTypeField);
$classUpdater->insertFieldAfter('addresses', $addressAccessTypeField);
$saveClass = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ services:
arguments:
- '@coreshop.repository.customer'
- '%coreshop.model.customer.class%'
- '%coreshop.customer.security.login_identifier%'

coreshop.security.customer.password_encoder_factory:
class: Pimcore\Security\Encoder\Factory\UserAwareEncoderFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ services:
- '%coreshop.folder.customer%'
- '%coreshop.folder.guest%'
- '%coreshop.folder.address%'
- '%coreshop.customer.security.login_identifier%'

coreshop.customer.login_service: '@CoreShop\Bundle\CoreBundle\Customer\CustomerLoginService'
CoreShop\Bundle\CoreBundle\Customer\CustomerLoginServiceInterface: '@CoreShop\Bundle\CoreBundle\Customer\CustomerLoginService'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ services:
- { name: kernel.event_listener, event: pimcore.dataobject.preDelete, method: checkCustomerOrdersBeforeDeletion }
- { name: kernel.event_listener, event: pimcore.dataobject.deleteInfo, method: checkCustomerDeletionAllowed }

CoreShop\Bundle\CoreBundle\EventListener\CustomerSecurityValidationListener:
arguments:
- '@coreshop.repository.customer'
- '%coreshop.model.customer.class%'
- '%coreshop.customer.security.login_identifier%'
tags:
- { name: kernel.event_listener, event: pimcore.dataobject.preUpdate, method: checkCustomerSecurityDataBeforeUpdate }

CoreShop\Bundle\CoreBundle\EventListener\QuantityRangeUnitValidationListener:
arguments:
- '@coreshop.repository.product_unit_definition'
Expand Down
20 changes: 16 additions & 4 deletions src/CoreShop/Bundle/CoreBundle/Security/ObjectUserProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,39 @@ class ObjectUserProvider implements UserProviderInterface
*/
protected $className;

/**
* @var string
*/
protected $loginIdentifier;

/**
* @param CustomerRepositoryInterface $customerRepository
* @param string $className
* @param string $loginIdentifier
*/
public function __construct(CustomerRepositoryInterface $customerRepository, $className)
public function __construct(
CustomerRepositoryInterface $customerRepository,
$className,
$loginIdentifier
)
{
$this->customerRepository = $customerRepository;
$this->className = $className;
$this->loginIdentifier = $loginIdentifier;
}

/**
* {@inheritdoc}
*/
public function loadUserByUsername($emailAddress)
public function loadUserByUsername($userNameOrEmailAddress)
{
$customer = $this->customerRepository->findCustomerByEmail($emailAddress);
$customer = $this->customerRepository->findUniqueByLoginIdentifier($this->loginIdentifier, $userNameOrEmailAddress, false);

if ($customer instanceof CustomerInterface) {
return $customer;
}

throw new UsernameNotFoundException(sprintf('User with email address %s was not found', $emailAddress));
throw new UsernameNotFoundException(sprintf('User with email address or username "%s" was not found', $userNameOrEmailAddress));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function getConfigTreeBuilder()
$rootNode
->children()
->scalarNode('driver')->defaultValue(CoreShopResourceBundle::DRIVER_DOCTRINE_ORM)->end()
->enumNode('login_identifier')->values(['email', 'username'])->defaultValue('email')->end()
->end();

$this->addStack($rootNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public function load(array $config, ContainerBuilder $container)
$this->registerStack('coreshop', $config['stack'], $container);
}

$container->setParameter('coreshop.customer.security.login_identifier', $config['login_identifier']);

$loader->load('services.yml');

$container
Expand Down
Loading

0 comments on commit e30f278

Please sign in to comment.