<?php
namespace WabsAuth\Helper;
use DateTimeImmutable;
use Exception;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Shopware\Core\Checkout\Customer\Aggregate\CustomerGroup\CustomerGroupEntity;
use Shopware\Core\Checkout\Customer\CustomerEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\HttpFoundation\Session\Session;
use WabsAuth\Helper\Keycloak\KeycloakProvider;
class AuthHelper
{
private SystemConfigService $systemConfigService;
private SalesChannelRepositoryInterface $countryRepository;
private SalesChannelRepositoryInterface $salutationRepository;
private NumberRangeValueGeneratorInterface $numberRangeValueGenerator;
private EntityRepositoryInterface $customerRepository;
private EntityRepositoryInterface $customerGroupRepository;
private EntityRepositoryInterface $wabsAuthCustomerRepository;
private array $groupMapping;
private bool $noAdvancedMode;
private Session $session;
private AddressMatchingHelper $addressMatchingHelper;
private DebugHelper $logger;
public bool $organizationAddressFlag = false;
/**
* AuthHelper constructor.
* @param SystemConfigService $systemConfigService
* @param SalesChannelRepositoryInterface $countryRepository
* @param SalesChannelRepositoryInterface $salutationRepository
* @param NumberRangeValueGeneratorInterface $numberRangeValueGenerator
* @param EntityRepositoryInterface $customerRepository
* @param EntityRepositoryInterface $wabsAuthCustomerRepository
* @param EntityRepositoryInterface $customerGroupRepository
* @param AddressMatchingHelper $addressMatchingHelper
* @param Session $session
* @param DebugHelper $logger
*/
public function __construct(
SystemConfigService $systemConfigService,
SalesChannelRepositoryInterface $countryRepository,
SalesChannelRepositoryInterface $salutationRepository,
NumberRangeValueGeneratorInterface $numberRangeValueGenerator,
EntityRepositoryInterface $customerRepository,
EntityRepositoryInterface $wabsAuthCustomerRepository,
EntityRepositoryInterface $customerGroupRepository,
AddressMatchingHelper $addressMatchingHelper,
Session $session,
DebugHelper $logger
) {
$this->systemConfigService = $systemConfigService;
$this->countryRepository = $countryRepository;
$this->salutationRepository = $salutationRepository;
$this->numberRangeValueGenerator = $numberRangeValueGenerator;
$this->customerRepository = $customerRepository;
$this->wabsAuthCustomerRepository = $wabsAuthCustomerRepository;
$this->customerGroupRepository = $customerGroupRepository;
$this->noAdvancedMode = (bool)$this->systemConfigService->get('WabsAuth.config.disableAdvancedMode');
$tmpGroupMapping = $this->systemConfigService->get('WabsAuth.config.customerGroupMapping');
$tmpGroupMapping = explode("\n", $tmpGroupMapping);
foreach ($tmpGroupMapping as $item) {
if (!empty($item)) {
[$igmWord, $groupName, $femaleTranslation, $maleTranslation] = explode(';', $item);
if ($igmWord && $groupName) {
$this->groupMapping[$igmWord] = [$groupName, $femaleTranslation ?? '', $maleTranslation ?? ''];
}
}
}
$this->session = $session;
$this->addressMatchingHelper = $addressMatchingHelper;
$this->logger = $logger;
}
/**
* Returns the OpenID Connector
*
* @return
*/
public function getProvider(): KeycloakProvider
{
$scopes = $this->systemConfigService->get('WabsAuth.config.scopes');
$scopes = explode(',', $scopes);
$scopes = array_map('trim', $scopes);
$algorithm = new Sha256();
return new KeycloakProvider([
'authServerUrl' => $this->systemConfigService->get('WabsAuth.config.authServerUrl'),
'realm' => $this->systemConfigService->get('WabsAuth.config.realm'),
'clientId' => $this->systemConfigService->get('WabsAuth.config.clientId'),
'clientSecret' => $this->systemConfigService->get('WabsAuth.config.clientSecret'),
'redirectUri' => $this->systemConfigService->get('WabsAuth.config.redirectUri'),
'encryptionAlgorithm' => $algorithm->algorithmId(),
'encryptionKey' => $this->systemConfigService->get('WabsAuth.config.publicKey'),
'scopes' => $scopes,
]);
}
/**
* Returns a valid customer or throws an exception, if creation or update of customer failed.
*
* @param array $ownerData
* @param string $accessToken
* @param SalesChannelContext $context
* @return bool|CustomerEntity
* @throws Exception
*/
public function getCustomer(array $ownerData, string $accessToken, SalesChannelContext $context)
{
// check if user exists in database and return entity
$ownerData['email'] = $this->getOwnerMailAddress($ownerData);
if (!$ownerData['email']) {
$this->logger->debug('AuthHelper | No Email Address provided.');
throw new Exception('E-Mail Address not available.');
}
/** @var CustomerEntity $customer */
$customer = $this->customerExists($ownerData, $context);
if (!$customer) {
$customer = $this->_register($ownerData, $context);
}
if (!$customer->getActive()) {
$this->logger->debug('AuthHelper | Customer not active.');
throw new Exception('Customer Account deactivated.');
}
$customerGroups = $this->getAllowedGroups($ownerData);
$customerGroupId = $this->getCustomerGroupId($ownerData, $customerGroups, $context);
if (empty($customerGroupId)) {
$this->logger->debug('AuthHelper | No Valid Group for customer with number: ' . $customer->getCustomerNumber());
throw new Exception('No valid Group for customer.');
}
if (empty($customerGroups)) {
$customerGroups[] = $customerGroups;
}
return $this->updateCustomer($customer, $ownerData, $customerGroupId, $customerGroups, $accessToken, $context);
}
/**
* Returns the email of the customer from the given array.
*
* @param array $data
* @return string
*/
protected function getOwnerMailAddress(array $data): string
{
if (filter_var($data['preferred_username'], FILTER_VALIDATE_EMAIL)) {
return $data['preferred_username'];
}
if (filter_var($data['igm_account_data']['accountName'], FILTER_VALIDATE_EMAIL)) {
return $data['igm_account_data']['accountName'];
}
return '';
}
/**
* Fetch current User by igm_uid
*
* @param array $data
* @param SalesChannelContext $context
*
* @return mixed|null
*/
protected function customerExists(array $data, SalesChannelContext $context)
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('customer.guest', 0));
$criteria->addFilter(new EqualsFilter('pluginWabsAuthCustomer.igmUid', $data['igm_account_data']['relId']));
$result = $this->customerRepository->search($criteria, $context->getContext());
if ($result->count() !== 1) {
$this->logger->debug('AuthHelper | No customer with email address.');
return null;
}
return $result->first();
}
/**
* Registers a new customer in the system.
*
* @param array $data
* @param SalesChannelContext $context
*
* @return CustomerEntity
*/
protected function _register(array $data, SalesChannelContext $context): CustomerEntity
{
/** get salutation uuid */
$gender = 'mr';
if ($data['igm_account_data']['gender'] === 'W') {
$gender .= 's';
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('salutationKey', $gender));
$salutation = $this->salutationRepository->search($criteria, $context)->first();
/** get country uuid | DE */
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', true));
$criteria->addFilter(new EqualsFilter('iso', 'DE'));
$country = $this->countryRepository->search($criteria, $context)->first();
$customer = [
'id' => Uuid::randomHex(),
'customerNumber' => $this->numberRangeValueGenerator->getValue(
$this->customerRepository->getDefinition()->getEntityName(),
$context->getContext(),
$context->getSalesChannel()->getId()
),
'salesChannelId' => $context->getSalesChannel()->getId(),
'languageId' => $context->getContext()->getLanguageId(),
'groupId' => $context->getCurrentCustomerGroup()->getId(),
'defaultPaymentMethodId' => $context->getPaymentMethod()->getId(),
'salutationId' => $salutation->getId(),
'firstName' => $data['given_name'] ?? '',
'lastName' => $data['family_name'] ?? '',
'email' => $data['email'],
'title' => '',
'affiliateCode' => '',
'campaignCode' => '',
'active' => true,
'birthday' => '',
'guest' => false,
'firstLogin' => new DateTimeImmutable(),
'defaultShippingAddressId' => null,
'defaultBillingAddressId' => null,
'addresses' => [],
];
$billingAddress = [];
$billingAddress['id'] = Uuid::randomHex();
$billingAddress['customerId'] = $customer['id'];
$billingAddress['firstName'] = $data['given_name'] ?? '';
$billingAddress['lastName'] = $data['family_name'] ?? '';
$billingAddress['salutationId'] = $salutation->getId();
/** @ToDo: Woher kommen diese Infos? */
/** Default: Aktuelle Daten des IGM Impressum */
$billingAddress['street'] = 'xxxxx';
$billingAddress['zipcode'] = 'xxxxx';
$billingAddress['city'] = 'xxxxx';
/** @ToDo: End */
$billingAddress['countryId'] = $country->getId();
$customer['defaultShippingAddressId'] = $billingAddress['id'];
$customer['defaultBillingAddressId'] = $billingAddress['id'];
$customer['addresses'][] = $billingAddress;
// Prüfen auf Funktionalität!
// $customer['pluginWabsAuthCustomer']['igmUid'] = $data['igm_account_data']['relId'];
// register new user with api data
$this->customerRepository->create([$customer], $context->getContext());
$criteria = new Criteria([$customer['id']]);
$criteria->addAssociation('addresses');
$criteria->addAssociation('salutation');
$customer = $this->customerRepository->search($criteria, $context->getContext())->first();
/** save relId */
$this->wabsAuthCustomerRepository->create([
[
'customerId' => $customer->getId(),
'igmUid' => $data['igm_account_data']['relId']
]
], $context->getContext());
/** @var CustomerEntity $customerEntity */
return $customer;
}
/**
* @param array $ownerData
* @return array
*/
private function getAllowedGroups(array $ownerData): array
{
$igmGroups = [];
if (array_key_exists('igm_authorization_groups', $ownerData)) {
foreach ($ownerData['igm_authorization_groups'] as $igmGroup) {
if (array_key_exists($igmGroup, $this->groupMapping)) {
$igmGroups[] = $this->groupMapping[$igmGroup];
}
}
}
return $igmGroups;
}
/**
* Returns true, if the user has multiple groups to choose from.
* Else returns false.
*
* @param array $ownerData
* @param array $igmGroups
* @param SalesChannelContext $context
*
* @return string
* @throws Exception
*/
public function getCustomerGroupId(
array $ownerData,
array $igmGroups,
SalesChannelContext $context
): string {
$autoLoginGroup = $this->session->get('oauth_auto_group');
if ($autoLoginGroup) {
$this->session->remove('oauth_auto_group');
$this->logger->debug('AuthHelper | Auto login is active (Group: ' . $autoLoginGroup . ').');
$this->logger->debug('AuthHelper | Available groups:' . var_export($igmGroups,true));
foreach ($igmGroups as $igmGroup) {
if (in_array($autoLoginGroup, $igmGroup)) {
$this->logger->debug('AuthHelper | Auto login returns ' . $autoLoginGroup . '.');
return $this->loadCustomerGroupId($autoLoginGroup, $context);
}
}
if( $this->session->get('oauth_force_auto_group') ) {
return '';
}
}
if (count($igmGroups) <= 0) {
if (!array_key_exists('igm_account_category', $ownerData) || empty($ownerData['igm_account_category'])) {
$this->logger->debug('AuthHelper | getCustomerGroupId | No group transmitted.');
return '';
}
$this->logger->debug('AuthHelper | getCustomerGroupId | igm_account_category used instead of groups.');
return $this->loadCustomerGroupId($ownerData['igm_account_category'], $context);
}
if (count($igmGroups) === 1) {
$this->logger->debug('AuthHelper | getCustomerGroupId | Customer had exactly one group so we use it.');
return $this->loadCustomerGroupId($igmGroups[0][0], $context);
}
$this->logger->debug('AuthHelper | getCustomerGroupId | Customer has multiple groups, so he is GC.');
return $this->loadCustomerGroupId('GC', $context);
}
/**
* Tries to set the group for the customer by simple name. If $groupName is not found, nothing happens.
*
* @param CustomerEntity $customer
* @param string $groupName
* @param SalesChannelContext $context
*
* @return CustomerEntity
*/
public function loadCustomerGroupId(
string $groupName,
SalesChannelContext $context
): string {
$criteria = new Criteria();
$possibleGroups = $this->customerGroupRepository->search($criteria,
$context->getContext())->getEntities()->getElements();
if ($this->noAdvancedMode) {
$groupName = 'Mitglied';
}
/** @var CustomerGroupEntity $group */
foreach ($possibleGroups as $group) {
if (strtolower($group->getName()) === strtolower($groupName)) {
return $group->getId();
}
}
return '';
}
/**
* Updates the customer with the given $data array values.
*
* @param CustomerEntity $customerEntity
* @param array $data
* @param string $customerGroup
* @param array $igmGroups
* @param string $accessToken
* @param SalesChannelContext $context
*
* @return CustomerEntity
*/
public function updateCustomer(
CustomerEntity $customerEntity,
array $data,
string $customerGroup,
array $igmGroups,
string $accessToken,
SalesChannelContext $context
): CustomerEntity {
$customFieldData = [
'igm_session_token' => $data['igm_session_token'],
'igm_account_data' => $data['igm_account_data'],
'access_token' => $accessToken,
'igm_groups' => $igmGroups
];
$this->logger->debug('AuthHelper | ARRAY customFieldData: ' . json_encode($customFieldData));
$this->logger->debug('AuthHelper | ARRAY data: ' . json_encode($customFieldData));
/** get salutation uuid */
$gender = 'mr';
if ($data['igm_account_data']['gender'] === 'W') {
$gender .= 's';
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('salutationKey', $gender));
$salutation = $this->salutationRepository->search($criteria, $context)->first();
$paymentMethodId = $this->addressMatchingHelper->getAppropriatePaymentMethodId($customerGroup);
$addressUUID = $this->addressMatchingHelper->createCustomerAddress(
$customerEntity,
$customerGroup,
$data['igm_session_token'],
$accessToken,
$data['igm_account_data'],
$context
);
if ($addressUUID === false) {
$this->organizationAddressFlag = true;
}
$this->logger->debug('AuthHelper | customer update $addressUUID: ' . json_encode($addressUUID));
$customer = [
'id' => $customerEntity->getId(),
'salutationId' => $salutation->getId(),
'firstName' => $data['igm_account_data']['firstname'] ?? $customerEntity->getFirstName(),
'lastName' => $data['igm_account_data']['lastname'] ?? $customerEntity->getLastName(),
'email' => $data['igm_account_data']['accountName'] ?? $customerEntity->getEmail(),
'customFields' => $customFieldData,
'groupId' => $customerGroup,
'lastLogin' => new DateTimeImmutable(),
'defaultPaymentMethodId' => $paymentMethodId,
'lastPaymentMethodId' => $paymentMethodId,
];
if (!empty($addressUUID)) {
$customer['defaultShippingAddressId'] = $addressUUID;
$customer['defaultBillingAddressId'] = $addressUUID;
}
$this->logger->debug('AuthHelper | ARRAY customer update data: ' . json_encode($customer));
// update user with api data
$this->customerRepository->update([$customer], $context->getContext());
$customerEntity = $this->addressMatchingHelper->reloadCustomer($customerEntity, $context);
$this->addressMatchingHelper->deleteUnnecessaryAddresses($customerEntity, $context);
return $customerEntity;
}
/**
* Loads the customer group of the user and adds them to the customer
*
* @param CustomerEntity $customer
* @param SalesChannelContext $context
* @return CustomerEntity|null
*/
public function loadCustomerGroupToCustomer(CustomerEntity $customer, SalesChannelContext $context): ?CustomerEntity
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('id', $customer->getGroupId()));
$customerGroup = $this->customerGroupRepository->search($criteria, $context->getContext())->first();
if ($customerGroup === null) {
$this->logger->debug('AuthHelper | No customer group could be loaded to customer.');
return $customer;
}
$customer->setGroup($customerGroup);
return $customer;
}
/**
* Checks if user in the GC group that is used to select groups. Checks then if the user has the
* $selectedGroup added to the account on login and therefore the permission to use the $selectedGroup.
*
* @param string $selectedGroup
* @param CustomerEntity $customer
* @return bool
*/
public function checkAllowedGroups(string $selectedGroup, CustomerEntity $customer): bool
{
$customerGroup = $customer->getGroup();
if ($this->noAdvancedMode) {
return true;
}
if ($customerGroup === null || $customerGroup->getName() !== 'GC') {
$this->logger->debug('AuthHelper | Customer has no group and is therefore not allowed to login.');
return false;
}
$customerCustomFields = $customer->getCustomFields();
if (!array_key_exists('igm_groups', $customerCustomFields)) {
$this->logger->debug('AuthHelper | igm_groups array key is missing in custom fields.');
return false;
}
$possibleGroups = $customerCustomFields['igm_groups'];
foreach ($possibleGroups as $group) {
if (in_array($selectedGroup, $group)) {
return true;
}
}
$this->logger->debug('AuthHelper | The customer group is not in the allowed groups.');
return false;
}
}