2020-06-19 00:42:10 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Model\Table;
|
|
|
|
|
|
|
|
use App\Model\Table\AppTable;
|
|
|
|
use Cake\ORM\Table;
|
|
|
|
use Cake\Validation\Validator;
|
2020-06-21 21:27:11 +02:00
|
|
|
use Cake\ORM\RulesChecker;
|
2020-06-22 17:47:11 +02:00
|
|
|
use Cake\ORM\TableRegistry;
|
2022-09-19 01:12:14 +02:00
|
|
|
use Cake\Event\EventInterface;
|
|
|
|
use Cake\Datasource\EntityInterface;
|
|
|
|
use Cake\Http\Session;
|
2021-09-24 01:48:50 +02:00
|
|
|
use Cake\Http\Client;
|
|
|
|
use Cake\Utility\Security;
|
|
|
|
use Cake\Core\Configure;
|
2021-11-24 14:46:34 +01:00
|
|
|
use Cake\Utility\Text;
|
2022-09-19 01:12:14 +02:00
|
|
|
use ArrayObject;
|
2020-06-19 00:42:10 +02:00
|
|
|
|
|
|
|
class UsersTable extends AppTable
|
|
|
|
{
|
|
|
|
public function initialize(array $config): void
|
|
|
|
{
|
|
|
|
parent::initialize($config);
|
2021-10-21 11:33:41 +02:00
|
|
|
$this->addBehavior('Timestamp');
|
2020-06-19 00:42:10 +02:00
|
|
|
$this->addBehavior('UUID');
|
2022-10-31 13:38:31 +01:00
|
|
|
$this->addBehavior('MetaFields');
|
2021-11-17 15:43:52 +01:00
|
|
|
$this->addBehavior('AuditLog');
|
2022-10-27 10:14:09 +02:00
|
|
|
$this->addBehavior('NotifyAdmins', [
|
2022-12-08 10:24:09 +01:00
|
|
|
'fields' => ['role_id', 'individual_id', 'organisation_id', 'disabled', 'modified', 'meta_fields'],
|
2022-10-27 10:14:09 +02:00
|
|
|
]);
|
2021-10-01 13:19:26 +02:00
|
|
|
$this->initAuthBehaviors();
|
2020-06-19 00:42:10 +02:00
|
|
|
$this->belongsTo(
|
|
|
|
'Individuals',
|
|
|
|
[
|
|
|
|
'dependent' => false,
|
|
|
|
'cascadeCallbacks' => false
|
|
|
|
]
|
|
|
|
);
|
|
|
|
$this->belongsTo(
|
|
|
|
'Roles',
|
|
|
|
[
|
|
|
|
'dependent' => false,
|
|
|
|
'cascadeCallbacks' => false
|
|
|
|
]
|
|
|
|
);
|
2021-11-24 01:25:32 +01:00
|
|
|
$this->belongsTo(
|
|
|
|
'Organisations',
|
|
|
|
[
|
|
|
|
'dependent' => false,
|
|
|
|
'cascadeCallbacks' => false
|
|
|
|
]
|
|
|
|
);
|
2021-10-08 10:27:40 +02:00
|
|
|
$this->hasMany(
|
|
|
|
'UserSettings',
|
|
|
|
[
|
|
|
|
'dependent' => true,
|
|
|
|
'cascadeCallbacks' => true
|
|
|
|
]
|
|
|
|
);
|
2020-06-21 21:27:11 +02:00
|
|
|
$this->setDisplayField('username');
|
2020-06-19 00:42:10 +02:00
|
|
|
}
|
|
|
|
|
2022-09-19 01:12:14 +02:00
|
|
|
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
|
|
|
{
|
2022-11-09 14:09:27 +01:00
|
|
|
if (isset($data['username'])) {
|
|
|
|
$data['username'] = trim(mb_strtolower($data['username']));
|
|
|
|
}
|
2022-09-19 01:12:14 +02:00
|
|
|
}
|
|
|
|
|
2022-09-21 10:11:09 +02:00
|
|
|
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
|
|
|
|
{
|
2022-12-08 10:19:28 +01:00
|
|
|
$success = true;
|
2022-10-21 09:00:49 +02:00
|
|
|
if (!$entity->isNew()) {
|
|
|
|
$success = $this->handleUserUpdateRouter($entity);
|
|
|
|
}
|
2022-09-21 10:11:09 +02:00
|
|
|
return $success;
|
|
|
|
}
|
|
|
|
|
2022-11-09 14:09:27 +01:00
|
|
|
private function checkPermissionRestrictions(EntityInterface $entity)
|
|
|
|
{
|
|
|
|
if (!isset($this->PermissionLimitations)) {
|
|
|
|
$this->PermissionLimitations = TableRegistry::get('PermissionLimitations');
|
|
|
|
}
|
|
|
|
$permissions = $this->PermissionLimitations->getListOfLimitations($entity);
|
|
|
|
foreach ($permissions as $permission_name => $permission) {
|
|
|
|
foreach ($permission as $scope => $permission_data) {
|
2022-12-08 15:39:28 +01:00
|
|
|
$valueToCompareTo = $permission_data['current'];
|
|
|
|
|
|
|
|
$enabled = false;
|
|
|
|
if (!empty($entity->meta_fields)) {
|
2022-11-09 14:09:27 +01:00
|
|
|
foreach ($entity['meta_fields'] as $metaField) {
|
|
|
|
if ($metaField['field'] === $permission_name) {
|
|
|
|
$enabled = true;
|
2022-12-08 15:39:28 +01:00
|
|
|
if ($metaField->isNew()) {
|
|
|
|
$valueToCompareTo += !empty($metaField->value) ? 1 : 0;
|
|
|
|
} else {
|
|
|
|
$valueToCompareTo += !empty($metaField->value) ? 0 : -1;
|
|
|
|
}
|
2022-11-09 14:09:27 +01:00
|
|
|
}
|
|
|
|
}
|
2022-12-08 15:39:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$enabled && !empty($entity->_metafields_to_delete)) {
|
|
|
|
foreach ($entity->_metafields_to_delete as $metaFieldToDelete) {
|
|
|
|
if ($metaFieldToDelete['field'] === $permission_name) {
|
|
|
|
$valueToCompareTo += !empty($metaFieldToDelete->value) ? -1 : 0;
|
|
|
|
}
|
2022-11-09 14:09:27 +01:00
|
|
|
}
|
|
|
|
}
|
2022-12-08 15:39:28 +01:00
|
|
|
|
2022-11-09 14:09:27 +01:00
|
|
|
if ($valueToCompareTo > $permission_data['limit']) {
|
|
|
|
return [
|
|
|
|
$permission_name =>
|
|
|
|
__(
|
|
|
|
'{0} limit exceeded.',
|
|
|
|
$scope
|
|
|
|
)
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-10-01 13:19:26 +02:00
|
|
|
private function initAuthBehaviors()
|
|
|
|
{
|
|
|
|
if (!empty(Configure::read('keycloak'))) {
|
|
|
|
$this->addBehavior('AuthKeycloak');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-19 00:42:10 +02:00
|
|
|
public function validationDefault(Validator $validator): Validator
|
|
|
|
{
|
|
|
|
$validator
|
2022-11-09 14:09:27 +01:00
|
|
|
->setStopOnFailure()
|
2020-11-06 10:07:25 +01:00
|
|
|
->requirePresence(['password'], 'create')
|
|
|
|
->add('password', [
|
|
|
|
'password_complexity' => [
|
|
|
|
'rule' => function($value, $context) {
|
2020-11-20 11:16:57 +01:00
|
|
|
if (!preg_match('/^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/s', $value) || strlen($value) < 12) {
|
2020-11-06 10:07:25 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
'message' => __('Invalid password. Passwords have to be either 16 character long or 12 character long with 3/4 special groups.')
|
|
|
|
],
|
|
|
|
'password_confirmation' => [
|
|
|
|
'rule' => function($value, $context) {
|
|
|
|
if (isset($context['data']['confirm_password'])) {
|
|
|
|
if ($context['data']['confirm_password'] !== $value) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
'message' => __('Password confirmation missing or not matching the password.')
|
|
|
|
]
|
2021-01-12 10:16:58 +01:00
|
|
|
])
|
2022-09-19 01:22:53 +02:00
|
|
|
->add('username', [
|
|
|
|
'username_policy' => [
|
|
|
|
'rule' => function($value, $context) {
|
|
|
|
if (mb_strlen(trim($value)) < 5 || mb_strlen(trim($value)) > 50) {
|
|
|
|
return __('Invalid username length. Make sure that you provide a username of at least 5 and up to 50 characters in length.');
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
]
|
|
|
|
])
|
2021-01-12 10:16:58 +01:00
|
|
|
->requirePresence(['username'], 'create')
|
2022-09-21 10:10:02 +02:00
|
|
|
->notEmptyString('username', __('Please fill this field'), 'create');
|
2023-01-03 15:03:06 +01:00
|
|
|
if (Configure::read('user.username-must-be-email')) {
|
|
|
|
$validator->add('username', 'valid_email', [
|
|
|
|
'rule' => 'email',
|
|
|
|
'message' => 'Username has to be a valid e-mail address.'
|
|
|
|
]);
|
|
|
|
}
|
2020-06-19 00:42:10 +02:00
|
|
|
return $validator;
|
|
|
|
}
|
2020-06-21 21:27:11 +02:00
|
|
|
|
|
|
|
public function buildRules(RulesChecker $rules): RulesChecker
|
|
|
|
{
|
2023-01-03 15:03:06 +01:00
|
|
|
$rules->add($rules->isUnique(['username']));
|
|
|
|
if (empty(Configure::read('user.multiple-users-per-individual')) || !empty(Configure::read('keycloak.enabled'))) {
|
|
|
|
$rules->add($rules->isUnique(['individual_id']));
|
|
|
|
}
|
2023-02-24 15:02:08 +01:00
|
|
|
|
|
|
|
$rules->add(function($entity, $options) {
|
|
|
|
$permissionRestrictionCheck = $this->checkPermissionRestrictions($entity);
|
|
|
|
if ($permissionRestrictionCheck !== true) {
|
|
|
|
foreach ($permissionRestrictionCheck as $permission_name => $errors) {
|
|
|
|
foreach ($entity->meta_fields as $i => $metaField) {
|
|
|
|
if ($metaField['field'] === $permission_name) {
|
|
|
|
$entity->meta_fields[$i]->setErrors(['value' => $errors]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}, 'permissionLimitations');
|
2020-06-21 21:27:11 +02:00
|
|
|
return $rules;
|
|
|
|
}
|
2020-06-22 17:45:00 +02:00
|
|
|
|
|
|
|
public function checkForNewInstance(): bool
|
|
|
|
{
|
|
|
|
if (empty($this->find()->first())) {
|
|
|
|
$this->Roles = TableRegistry::get('Roles');
|
|
|
|
$role = $this->Roles->newEntity([
|
|
|
|
'name' => 'admin',
|
2021-11-24 23:24:04 +01:00
|
|
|
'perm_admin' => 1,
|
|
|
|
'perm_org_admin' => 1,
|
|
|
|
'perm_sync' => 1
|
2020-06-22 17:45:00 +02:00
|
|
|
]);
|
|
|
|
$this->Roles->save($role);
|
2021-11-24 14:46:34 +01:00
|
|
|
$this->Organisations = TableRegistry::get('Organisations');
|
|
|
|
$organisation = $this->Organisations->newEntity([
|
2021-11-24 23:59:34 +01:00
|
|
|
'name' => 'default_organisation',
|
|
|
|
'uuid' => Text::uuid()
|
2021-11-24 14:46:34 +01:00
|
|
|
]);
|
|
|
|
$this->Organisations->save($organisation);
|
2020-06-22 17:45:00 +02:00
|
|
|
$this->Individuals = TableRegistry::get('Individuals');
|
2020-06-22 17:52:11 +02:00
|
|
|
$individual = $this->Individuals->newEntity([
|
2020-06-22 17:54:19 +02:00
|
|
|
'email' => 'admin@admin.test',
|
|
|
|
'first_name' => 'admin',
|
|
|
|
'last_name' => 'admin'
|
2020-06-22 17:45:00 +02:00
|
|
|
]);
|
|
|
|
$this->Individuals->save($individual);
|
|
|
|
$user = $this->newEntity([
|
|
|
|
'username' => 'admin',
|
|
|
|
'password' => 'Password1234',
|
2020-06-22 17:50:10 +02:00
|
|
|
'individual_id' => $individual->id,
|
2021-11-25 00:02:16 +01:00
|
|
|
'organisation_id' => $organisation->id,
|
2020-06-22 17:50:10 +02:00
|
|
|
'role_id' => $role->id
|
2020-06-22 17:45:00 +02:00
|
|
|
]);
|
|
|
|
$this->save($user);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2021-09-24 01:48:50 +02:00
|
|
|
|
2021-10-01 13:19:26 +02:00
|
|
|
public function captureIndividual($user): int
|
2021-09-24 01:48:50 +02:00
|
|
|
{
|
|
|
|
$individual = $this->Individuals->find()->where(['email' => $user['individual']['email']])->first();
|
|
|
|
if (empty($individual)) {
|
|
|
|
$individual = $this->Individuals->newEntity($user['individual']);
|
|
|
|
if (!$this->Individuals->save($individual)) {
|
|
|
|
throw new BadRequestException(__('Could not save the associated individual'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $individual->id;
|
|
|
|
}
|
|
|
|
|
2021-10-01 13:19:26 +02:00
|
|
|
public function captureOrganisation($user): int
|
2021-09-24 01:48:50 +02:00
|
|
|
{
|
|
|
|
$organisation = $this->Organisations->find()->where(['uuid' => $user['organisation']['uuid']])->first();
|
|
|
|
if (empty($organisation)) {
|
|
|
|
$user['organisation']['name'] = $user['organisation']['uuid'];
|
|
|
|
$organisation = $this->Organisations->newEntity($user['organisation']);
|
|
|
|
if (!$this->Organisations->save($organisation)) {
|
|
|
|
throw new BadRequestException(__('Could not save the associated organisation'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $organisation->id;
|
|
|
|
}
|
|
|
|
|
2021-10-01 13:19:26 +02:00
|
|
|
public function captureRole($user): int
|
2021-09-24 01:48:50 +02:00
|
|
|
{
|
|
|
|
$role = $this->Roles->find()->where(['name' => $user['role']['name']])->first();
|
|
|
|
if (empty($role)) {
|
|
|
|
if (empty($role)) {
|
|
|
|
throw new NotFoundException(__('Invalid role'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $role->id;
|
|
|
|
}
|
|
|
|
|
2021-10-01 13:19:26 +02:00
|
|
|
public function enrollUserRouter($data): void
|
2021-09-24 01:48:50 +02:00
|
|
|
{
|
2021-10-01 13:19:26 +02:00
|
|
|
if (!empty(Configure::read('keycloak'))) {
|
|
|
|
$this->enrollUser($data);
|
2021-09-24 01:48:50 +02:00
|
|
|
}
|
|
|
|
}
|
2022-09-21 10:11:09 +02:00
|
|
|
|
|
|
|
public function handleUserUpdateRouter(\App\Model\Entity\User $user): bool
|
|
|
|
{
|
2022-12-16 15:32:29 +01:00
|
|
|
if (!empty(Configure::read('keycloak.enabled'))) {
|
2022-09-21 10:11:09 +02:00
|
|
|
$success = $this->handleUserUpdate($user);
|
2022-10-27 10:14:09 +02:00
|
|
|
// return $success;
|
2022-09-21 10:11:09 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2020-06-19 00:42:10 +02:00
|
|
|
}
|