Merge branch 'develop' of github.com:cerebrate-project/cerebrate into develop-unstable
commit
3ef64911f9
|
@ -249,6 +249,12 @@ class CRUDComponent extends Component
|
|||
if (!empty($params['fields'])) {
|
||||
$patchEntityParams['fields'] = $params['fields'];
|
||||
}
|
||||
if (isset($params['beforeMarshal'])) {
|
||||
$input = $params['beforeMarshal']($input);
|
||||
if ($input === false) {
|
||||
throw new NotFoundException(__('Could not save {0} due to the marshaling failing. Your input is bad and you should feel bad.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
if ($this->metaFieldsSupported()) {
|
||||
$massagedData = $this->massageMetaFields($data, $input, $metaTemplates);
|
||||
unset($input['MetaTemplates']); // Avoid MetaTemplates to be overriden when patching entity
|
||||
|
@ -500,6 +506,12 @@ class CRUDComponent extends Component
|
|||
if (!empty($params['fields'])) {
|
||||
$patchEntityParams['fields'] = $params['fields'];
|
||||
}
|
||||
if (isset($params['beforeMarshal'])) {
|
||||
$input = $params['beforeMarshal']($input);
|
||||
if ($input === false) {
|
||||
throw new NotFoundException(__('Could not save {0} due to the marshaling failing. Your input is bad and you should feel bad.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
if ($this->metaFieldsSupported()) {
|
||||
$massagedData = $this->massageMetaFields($data, $input, $metaTemplates);
|
||||
unset($input['MetaTemplates']); // Avoid MetaTemplates to be overriden when patching entity
|
||||
|
@ -509,6 +521,9 @@ class CRUDComponent extends Component
|
|||
$data = $this->Table->patchEntity($data, $input, $patchEntityParams);
|
||||
if (isset($params['beforeSave'])) {
|
||||
$data = $params['beforeSave']($data);
|
||||
if ($data === false) {
|
||||
throw new NotFoundException(__('Could not save {0} due to the input failing to meet expectations. Your input is bad and you should feel bad.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
$savedData = $this->Table->save($data);
|
||||
if ($savedData !== false) {
|
||||
|
|
|
@ -6,6 +6,7 @@ use Cake\ORM\TableRegistry;
|
|||
use Cake\Http\Exception\UnauthorizedException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Utility\Security;
|
||||
|
||||
class UsersController extends AppController
|
||||
{
|
||||
|
@ -41,15 +42,30 @@ class UsersController extends AppController
|
|||
{
|
||||
$currentUser = $this->ACL->getUser();
|
||||
$validRoles = [];
|
||||
$individuals_params = [
|
||||
'sort' => ['email' => 'asc']
|
||||
];
|
||||
$individual_ids = [];
|
||||
if (!$currentUser['role']['perm_admin']) {
|
||||
$validRoles = $this->Users->Roles->find('list')->select(['id', 'name'])->order(['name' => 'asc'])->where(['perm_admin' => 0])->all()->toArray();
|
||||
$validRoles = $this->Users->Roles->find('list')->select(['id', 'name'])->order(['name' => 'asc'])->where(['perm_admin' => 0, 'perm_org_admin' => 0])->all()->toArray();
|
||||
$individual_ids = $this->Users->Individuals->find('aligned', ['organisation_id' => $currentUser['organisation_id']])->all()->extract('id')->toArray();
|
||||
if (empty($individual_ids)) {
|
||||
$individual_ids = [-1];
|
||||
}
|
||||
$individuals_params['conditions'] = ['id IN' => $individual_ids];
|
||||
} else {
|
||||
$validRoles = $this->Users->Roles->find('list')->order(['name' => 'asc'])->all()->toArray();
|
||||
}
|
||||
$defaultRole = $this->Users->Roles->find()->select(['id'])->first()->toArray();
|
||||
|
||||
$individuals = $this->Users->Individuals->find('list', $individuals_params)->toArray();
|
||||
$this->CRUD->add([
|
||||
'beforeSave' => function($data) use ($currentUser, $validRoles, $defaultRole) {
|
||||
'beforeMarshal' => function($data) {
|
||||
if (empty($data['password'])) {
|
||||
$data['password'] = Security::randomString(20);
|
||||
}
|
||||
return $data;
|
||||
},
|
||||
'beforeSave' => function($data) use ($currentUser, $validRoles, $defaultRole, $individual_ids) {
|
||||
if (!isset($data['role_id']) && !empty($defaultRole)) {
|
||||
$data['role_id'] = $defaultRole['id'];
|
||||
}
|
||||
|
@ -59,6 +75,21 @@ class UsersController extends AppController
|
|||
throw new MethodNotAllowedException(__('You do not have permission to assign that role.'));
|
||||
}
|
||||
}
|
||||
if ((!isset($data['individual_id']) || $data['individual_id'] === 'new') && !empty($data['individual'])) {
|
||||
$existingOrg = $this->Users->Organisations->find('all')->where(['id' => $data['organisation_id']])->select(['uuid'])->first();
|
||||
if (empty($existingOrg)) {
|
||||
throw new MethodNotAllowedException(__('No valid organisation found. Either encode the organisation separately or select a valid one.'));
|
||||
}
|
||||
$data['individual']['alignments'][] = ['type' => 'Member', 'organisation' => ['uuid' => $existingOrg['uuid']]];
|
||||
$data['individual_id'] = $this->Users->Individuals->captureIndividual($data['individual']);
|
||||
} else if (!$currentUser['role']['perm_admin'] && isset($data['individual_id'])) {
|
||||
if (!in_array($data['individual_id'], $individual_ids)) {
|
||||
throw new MethodNotAllowedException(__('The selected individual is not aligned with your organisation. Creating a user for them is not permitted.'));
|
||||
}
|
||||
}
|
||||
if (empty($data['individual_id'])) {
|
||||
throw new MethodNotAllowedException(__('No valid individual found. Either supply it in the request or set the individual_id to a valid value.'));
|
||||
}
|
||||
$this->Users->enrollUserRouter($data);
|
||||
return $data;
|
||||
}
|
||||
|
@ -81,9 +112,7 @@ class UsersController extends AppController
|
|||
}
|
||||
$dropdownData = [
|
||||
'role' => $validRoles,
|
||||
'individual' => $this->Users->Individuals->find('list', [
|
||||
'sort' => ['email' => 'asc']
|
||||
]),
|
||||
'individual' => $individuals,
|
||||
'organisation' => $this->Users->Organisations->find('list', [
|
||||
'sort' => ['name' => 'asc'],
|
||||
'conditions' => $org_conditions
|
||||
|
@ -115,7 +144,7 @@ class UsersController extends AppController
|
|||
$currentUser = $this->ACL->getUser();
|
||||
$validRoles = [];
|
||||
if (!$currentUser['role']['perm_admin']) {
|
||||
$validRoles = $this->Users->Roles->find('list')->select(['id', 'name'])->order(['name' => 'asc'])->where(['perm_admin' => 0])->all()->toArray();
|
||||
$validRoles = $this->Users->Roles->find('list')->select(['id', 'name'])->order(['name' => 'asc'])->where(['perm_admin' => 0, 'perm_org_admin' => 0])->all()->toArray();
|
||||
} else {
|
||||
$validRoles = $this->Users->Roles->find('list')->order(['name' => 'asc'])->all()->toArray();
|
||||
}
|
||||
|
@ -133,7 +162,7 @@ class UsersController extends AppController
|
|||
$params = [
|
||||
'get' => [
|
||||
'fields' => [
|
||||
'id', 'individual_id', 'role_id', 'username', 'disabled'
|
||||
'id', 'individual_id', 'role_id', 'disabled', 'username'
|
||||
]
|
||||
],
|
||||
'removeEmpty' => [
|
||||
|
@ -145,12 +174,10 @@ class UsersController extends AppController
|
|||
];
|
||||
if (!empty($this->ACL->getUser()['role']['perm_admin'])) {
|
||||
$params['fields'][] = 'individual_id';
|
||||
$params['fields'][] = 'username';
|
||||
$params['fields'][] = 'role_id';
|
||||
$params['fields'][] = 'organisation_id';
|
||||
$params['fields'][] = 'disabled';
|
||||
} else if (!empty($this->ACL->getUser()['role']['perm_org_admin'])) {
|
||||
$params['fields'][] = 'username';
|
||||
$params['fields'][] = 'role_id';
|
||||
$params['fields'][] = 'disabled';
|
||||
if (!$currentUser['role']['perm_admin']) {
|
||||
|
|
|
@ -42,6 +42,7 @@ class AlignmentsTable extends AppTable
|
|||
'type' => $type
|
||||
];
|
||||
$this->patchEntity($alignment, $data);
|
||||
$this->save($alignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace App\Model\Table;
|
|||
use App\Model\Table\AppTable;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use Cake\ORM\Query;
|
||||
|
||||
|
||||
class IndividualsTable extends AppTable
|
||||
{
|
||||
|
@ -59,7 +61,9 @@ class IndividualsTable extends AppTable
|
|||
'uuid' => $individual['uuid']
|
||||
])->first();
|
||||
} else {
|
||||
return null;
|
||||
$existingIndividual = $this->find()->where([
|
||||
'email' => $individual['email']
|
||||
])->first();
|
||||
}
|
||||
if (empty($existingIndividual)) {
|
||||
$entityToSave = $this->newEmptyEntity();
|
||||
|
@ -94,4 +98,16 @@ class IndividualsTable extends AppTable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function findAligned(Query $query, array $options)
|
||||
{
|
||||
$query = $query->select(['Individuals.id']);
|
||||
if (empty($options['organisation_id'])) {
|
||||
$query->leftJoinWith('Alignments')->where(['Alignments.organisation_id IS' => null]);
|
||||
} else {
|
||||
$query->innerJoinWith('Alignments')
|
||||
->where(['Alignments.organisation_id IN' => $options['organisation_id']]);
|
||||
}
|
||||
return $query->group(['Individuals.id', 'Individuals.uuid']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,17 @@ class CerebrateSettingsProvider extends BaseSettingsProvider
|
|||
],
|
||||
'Authentication' => [
|
||||
'Providers' => [
|
||||
'PasswordAuth' => [
|
||||
'password_auth.enabled' => [
|
||||
'name' => 'Disable password authentication',
|
||||
'type' => 'boolean',
|
||||
'severity' => 'warning',
|
||||
'description' => __('Enable username/password authentication.'),
|
||||
'default' => true,
|
||||
'test' => 'testEnabledAuth',
|
||||
'authentication_type' => 'password_auth'
|
||||
],
|
||||
],
|
||||
'KeyCloak' => [
|
||||
'keycloak.enabled' => [
|
||||
'name' => 'Enabled',
|
||||
|
@ -158,6 +169,8 @@ class CerebrateSettingsProvider extends BaseSettingsProvider
|
|||
'severity' => 'warning',
|
||||
'description' => __('Enable keycloak authentication'),
|
||||
'default' => false,
|
||||
'test' => 'testEnabledAuth',
|
||||
'authentication_type' => 'keycloak'
|
||||
],
|
||||
'keycloak.provider.applicationId' => [
|
||||
'name' => 'Client ID',
|
||||
|
@ -374,4 +387,24 @@ class CerebrateSettingValidator extends SettingValidator
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function testEnabledAuth($value, &$setting)
|
||||
{
|
||||
$providers = [
|
||||
'password_auth',
|
||||
'keycloak'
|
||||
];
|
||||
if (!$value) {
|
||||
$foundEnabledAuth = __('Cannot make change - this would disable every possible authentication method.');
|
||||
foreach ($providers as $provider) {
|
||||
if ($provider !== $setting['authentication_type']) {
|
||||
if (Configure::read($provider . '.enable')) {
|
||||
$foundEnabledAuth = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $foundEnabledAuth;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
<?php
|
||||
use Cake\Core\Configure;
|
||||
$passwordRequired = false;
|
||||
if ($this->request->getParam('action') === 'add') {
|
||||
$dropdownData['individual'] = ['new' => __('New individual')] + $dropdownData['individual'];
|
||||
if (!Configure::check('password_auth.enabled') || Configure::read('password_auth.enabled')) {
|
||||
$passwordRequired = 'required';
|
||||
}
|
||||
}
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => __('Roles define global rules for a set of users, including first and foremost access controls to certain functionalities.'),
|
||||
|
@ -10,6 +18,32 @@
|
|||
'label' => __('Associated individual'),
|
||||
'options' => $dropdownData['individual']
|
||||
],
|
||||
[
|
||||
'field' => 'individual.email',
|
||||
'stateDependence' => [
|
||||
'source' => '#individual_id-field',
|
||||
'option' => 'new'
|
||||
],
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'field' => 'individual.first_name',
|
||||
'label' => 'First name',
|
||||
'stateDependence' => [
|
||||
'source' => '#individual_id-field',
|
||||
'option' => 'new'
|
||||
],
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'field' => 'individual.last_name',
|
||||
'label' => 'Last name',
|
||||
'stateDependence' => [
|
||||
'source' => '#individual_id-field',
|
||||
'option' => 'new'
|
||||
],
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'field' => 'username',
|
||||
'autocomplete' => 'off'
|
||||
|
@ -25,16 +59,18 @@
|
|||
'field' => 'password',
|
||||
'label' => __('Password'),
|
||||
'type' => 'password',
|
||||
'required' => $this->request->getParam('action') === 'add' ? 'required' : false,
|
||||
'required' => $passwordRequired,
|
||||
'autocomplete' => 'new-password',
|
||||
'value' => ''
|
||||
'value' => '',
|
||||
'requirements' => (bool)$passwordRequired
|
||||
],
|
||||
[
|
||||
'field' => 'confirm_password',
|
||||
'label' => __('Confirm Password'),
|
||||
'type' => 'password',
|
||||
'required' => $this->request->getParam('action') === 'add' ? 'required' : false,
|
||||
'autocomplete' => 'off'
|
||||
'required' => $passwordRequired,
|
||||
'autocomplete' => 'off',
|
||||
'requirements' => (bool)$passwordRequired
|
||||
],
|
||||
[
|
||||
'field' => 'role_id',
|
||||
|
|
|
@ -12,26 +12,29 @@ use Cake\Core\Configure;
|
|||
'style' => ['filter: drop-shadow(4px 4px 4px #924da666);']
|
||||
])
|
||||
);
|
||||
echo sprintf('<h4 class="text-uppercase fw-light mb-3">%s</h4>', __('Sign In'));
|
||||
$template = [
|
||||
'inputContainer' => '<div class="form-floating input {{type}}{{required}}">{{content}}</div>',
|
||||
'formGroup' => '{{input}}{{label}}',
|
||||
'submitContainer' => '<div class="submit d-grid">{{content}}</div>',
|
||||
];
|
||||
$this->Form->setTemplates($template);
|
||||
echo $this->Form->create(null, ['url' => ['controller' => 'users', 'action' => 'login']]);
|
||||
echo $this->Form->control('username', ['label' => 'Username', 'class' => 'form-control mb-2', 'placeholder' => __('Username')]);
|
||||
echo $this->Form->control('password', ['type' => 'password', 'label' => 'Password', 'class' => 'form-control mb-3', 'placeholder' => __('Password')]);
|
||||
echo $this->Form->control(__('Login'), ['type' => 'submit', 'class' => 'btn btn-primary']);
|
||||
echo $this->Form->end();
|
||||
if (!empty(Configure::read('security.registration.self-registration'))) {
|
||||
echo '<div class="text-end">';
|
||||
echo sprintf('<span class="text-secondary ms-auto" style="font-size: 0.8rem">%s <a href="/users/register" class="text-decoration-none link-primary fw-bold">%s</a></span>', __('Doesn\'t have an account?'), __('Sign up'));
|
||||
echo '</div>';
|
||||
if (!Configure::check('password_auth.enabled') || Configure::read('password_auth.enabled')) {
|
||||
echo sprintf('<h4 class="text-uppercase fw-light mb-3">%s</h4>', __('Sign In'));
|
||||
echo $this->Form->create(null, ['url' => ['controller' => 'users', 'action' => 'login']]);
|
||||
echo $this->Form->control('username', ['label' => 'Username', 'class' => 'form-control mb-2', 'placeholder' => __('Username')]);
|
||||
echo $this->Form->control('password', ['type' => 'password', 'label' => 'Password', 'class' => 'form-control mb-3', 'placeholder' => __('Password')]);
|
||||
echo $this->Form->control(__('Login'), ['type' => 'submit', 'class' => 'btn btn-primary']);
|
||||
echo $this->Form->end();
|
||||
if (!empty(Configure::read('security.registration.self-registration'))) {
|
||||
echo '<div class="text-end">';
|
||||
echo sprintf('<span class="text-secondary ms-auto" style="font-size: 0.8rem">%s <a href="/users/register" class="text-decoration-none link-primary fw-bold">%s</a></span>', __('Don\'t have an account?'), __('Sign up'));
|
||||
echo '</div>';
|
||||
}
|
||||
if (!empty(Configure::read('keycloak.enabled'))) {
|
||||
echo sprintf('<div class="d-flex align-items-center my-2"><hr class="d-inline-block flex-grow-1"/><span class="mx-3 fw-light">%s</span><hr class="d-inline-block flex-grow-1"/></div>', __('Or'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty(Configure::read('keycloak.enabled'))) {
|
||||
echo sprintf('<div class="d-flex align-items-center my-2"><hr class="d-inline-block flex-grow-1"/><span class="mx-3 fw-light">%s</span><hr class="d-inline-block flex-grow-1"/></div>', __('Or'));
|
||||
echo $this->Form->create(null, [
|
||||
'url' => Cake\Routing\Router::url([
|
||||
'prefix' => false,
|
||||
|
|
|
@ -17,8 +17,11 @@ echo $this->element(
|
|||
'path' => 'username'
|
||||
],
|
||||
[
|
||||
'type' => 'generic',
|
||||
'key' => __('Email'),
|
||||
'path' => 'individual.email'
|
||||
'path' => 'individual.email',
|
||||
'url' => '/individuals/view/{{0}}',
|
||||
'url_vars' => 'individual_id'
|
||||
],
|
||||
[
|
||||
'type' => 'generic',
|
||||
|
|
Loading…
Reference in New Issue