Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OPES-1549: Allow to delete users #206

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

AaronGilMartinez
Copy link
Contributor

Fixes #184

@AaronGilMartinez AaronGilMartinez force-pushed the disable-delete-user branch 3 times, most recently from 4b81d7f to 3b80ff9 Compare February 11, 2025 08:41
@AaronGilMartinez AaronGilMartinez marked this pull request as ready for review February 11, 2025 08:43
@@ -25,6 +26,7 @@ Feature: Authentication
And I fill in "Application assurance levels" with "assurance"
And I fill in "Application available ticket types" with "ticket.test"
And I check the box "Force two factor authentication"
And I uncheck the box "Disable the user delete options"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't one piece missing here in the section below, like "And the ... checkbox should not be checked"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True

@@ -1,3 +1,6 @@
administer authentication configuration:
title: 'Administer Authentication configuration'
restrict access: false
delete users:
title: 'Allows the user to use delete user options in "User cancel form"'
restrict access: true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel uneasy about introducing a permission with such a generic name that could easily clash with permissions introduced by other modules or by future Drupal core versions.

If this has been agreed with others, then ok.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ofc, if any other module or core is coming up with a similar option and/or permission, we do likely have a problem either way, independent of the name clash.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This said, I don't have a good suggestion on namespacing the permission.
Permission machine names are typically not strictly namespaced, but they usually contain some piece that make them less likely to clash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name seems a bit generic indeed.
Since the permission is making available the options, maybe something related to what is doing would make more sense and hard to clash, like:

  • use delete user options
  • delete user options access
  • access delete user options

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to: access user delete options
Describes what it does and has a similar name to the configuration which helps to see a relation between each other.
It's more concise to avoid collision with other possible names.

@@ -13,7 +13,16 @@ declare(strict_types=1);
* Remove the option to delete users while keeping the option to block them.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line needs to change now.
Also, to follow conventions it would say "Removes..." instead of "Remove...".

I like "permanently delete" to make it more clear. With this, I think we no longer need to mention "while keeping the option to block them". But can be discussed.

Suggested change
* Remove the option to delete users while keeping the option to block them.
* Removes the option to permanently delete users, depending on settings and permissions.

or

Suggested change
* Remove the option to delete users while keeping the option to block them.
* Conditionally removes the option to permanently delete users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any of the two is ok for me.
About 'permanently' I'm not sure about it, is not that you can delete temporarily.
The entity is deleted or not, seems more a problem of the options themselves that aren't clear enough.


if (
$disabled_user_delete !== TRUE ||
$current_user->id() == 1 ||

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't ->hasPermission() already cover the user 1 case?
I mean it returns true for user 1 for all perms, right?

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, actually this includes admin users.
The way permissions are calculated is: is it admin or has the permission?
Which grants access to the users.
Not sure about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I want to keep functionality as is, I think is better to check the permission directly.

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discard the direct comparisson, inverting the option seems a better idea.
We have two options to allow to see the options:

  • When option is enabled or user has the permission.
    • Problem here is that we are granting access to other users than 1: admin users.
  • (preferred) When user is superuser or (option is enabled and has the permission).
    • This requires two conditions for the users to get access to the options, but keeps bc compatibility.

@@ -20,3 +20,6 @@ oe_authentication.settings:
force_2fa:
type: boolean
label: 'Force two factor authentication'
disable_user_delete:
type: boolean
label: 'Disable the user delete options'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More accurate than disable_user_delete would be restrict_user_delete.
Then for the description "Require a dedicated permission to permanently delete users."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverted the option so the name has changed totally.

@@ -1,3 +1,6 @@
administer authentication configuration:
title: 'Administer Authentication configuration'
restrict access: false
delete users:
title: 'Allows the user to use delete user options in "User cancel form"'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a permission title we don't say "Allows a user", we just describe the action itself.
(More can be in a description if needed)

Suggested change
title: 'Allows the user to use delete user options in "User cancel form"'
title: 'Use the delete user options in "User cancel form"'

Besides this, I like the phrase "permanently delete" to distinguish from other cancel options.

Suggested change
title: 'Allows the user to use delete user options in "User cancel form"'
title: 'Permanently delete users in the "User cancel form"'

TBD if you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used first one.

@@ -1,3 +1,6 @@
administer authentication configuration:
title: 'Administer Authentication configuration'
restrict access: false
delete users:
title: 'Allows the user to use delete user options in "User cancel form"'
restrict access: true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ofc, if any other module or core is coming up with a similar option and/or permission, we do likely have a problem either way, independent of the name clash.

@@ -1,3 +1,6 @@
administer authentication configuration:
title: 'Administer Authentication configuration'
restrict access: false
delete users:
title: 'Allows the user to use delete user options in "User cancel form"'
restrict access: true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This said, I don't have a good suggestion on namespacing the permission.
Permission machine names are typically not strictly namespaced, but they usually contain some piece that make them less likely to clash.

\Drupal::configFactory()->getEditable('oe_authentication.settings')
->set('disable_user_delete', TRUE)
->save();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we 100% rely on the update hook, or do we add ?? TRUE in respective places to protect site that did not run their update hooks?
Just because this is a security-relevant feature.

(Btw we don't need a ?? TRUE if instead the setting is inverted, so it would be "Bypass the delete users permission" rather than "Require the delete users permission".)

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module relied on update_hooks for other configuration, I did not want to add it just to this configuration, feels something should be done for all the configuration keys but is not in the scope.

Yes, I guess that the setting could be inverted, would mean no path update. I'm considering it.

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverted the config at the end so no update path is needed.

@@ -58,6 +58,11 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#title' => $this->t('Force two factor authentication'),
'#default_value' => $this->config(static::CONFIG_NAME)->get('force_2fa'),
];
$form['disable_user_delete'] = [
'#type' => 'checkbox',
'#title' => $this->t('Disable the user delete options'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about

Suggested change
'#title' => $this->t('Disable the user delete options'),
'#title' => $this->t('Restrict the options to permanently delete users'),
'#description' => $this->t('With this option enabled, only users with the *** permission will be allowed to permanently delete users.'),

(but see other comment below about radios)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title no longer applies, but I like the description.

'#type' => 'checkbox',
'#title' => $this->t('Disable the user delete options'),
'#default_value' => $this->config(static::CONFIG_NAME)->get('disable_user_delete'),
];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative would be to use radios for this, instead of single checkbox.
But not 100% sure. If the wording for a single checkbox is good, then we don't need radios.

The internal setting can remain a boolean.

With radios we can properly describe the effect of each option, without saying "if this is checked" or "if this is unchecked".

E.g.

  • ( ) Any user with the permission to cancel accounts can choose to permanently delete that user.
  • ( ) Only users with the *** permission can permanently delete users.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I think about it more, wouldn't this setting belong into "Account settings" form, next to "When canceling a user account"?

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module uses the checkbox for other boolean fields, like 2fa authentication.
And for the place, since it's something the module it's doing seems the right place with other configuration.

$disabled_user_delete !== TRUE ||
$current_user->id() == 1 ||
$current_user->hasPermission('delete users')
) {
return;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(side note)
In the code below we unset the respective options.
But I see that the items in function user_cancel_methods() have an optional 'access' key, which could be set to hide options.
However I am not really sure about the impact, so let's leave this part as is for now, and just keep it in mind for later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea of modifing/refactoring the current code/behaviour just the minimum to allow the deletion.

Copy link

@donquixote donquixote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First batch of feedback.
I want to submit this already, but I still want to have a closer look at the new tests.

Scenario: Users should not be able to delete other users
Given I am logged in as a user with the "administer users" permissions
When I visit my user page
And I click "Edit"
And I click "Cancel account"
Then I should see "Disable the account and keep its content."
And I should not see "Delete the account and its content."

Scenario: As a user with permission "delete users" I can access the options
Given I am logged in as a user with the "administer users, delete users" permissions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are only testing delete own here, so we should not grant the "administer users" permission.
Instead, give "Cancel own user account", and "Select method for cancelling account".

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact the pre-existing scenario has the same problem.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we rely on the fact that with "Cancel own user account", and "Select method for cancelling account" the effect of "delete users" would be the same as with "administer users"?
Normally we try to avoid testing stuff that is not controlled by our module. But here it seems quite the overlap / not clean separation.

Copy link
Contributor Author

@AaronGilMartinez AaronGilMartinez Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say yes, they are two paths to get to the same destination.
Indeed

And I click "Edit"
And I click "Cancel account"
Then I should see "Disable the account and keep its content."
And I should see "Delete the account and its content."

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should cover all 4 methods, whether they are available or not.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in core the 4th method already has a different access behavior than the 3rd method.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a behat test can we just assert all options? E.g. the options for a field are A, B, C. So that, if option D appears, we get a fail.
I don't like the "I should not see ..." because this can lead to blind spots if we misspell the (not) expected text.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, ideally we would assert that there are only 3 options and that they are the right ones.
No need of checking not available options.
Not sure if behat provides steps for this, I check, if not it will mean custom steps that I prefer to avoid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added checks for all the options, I think the best for the negative assertions is to use "Delete the account" as you suggest.


@DrupalLogin
Scenario: Users should not be able to delete other users
Given I am logged in as a user with the "administer users" permissions
When I visit my user page
And I click "Edit"
And I click "Cancel account"
Then I should see "Disable the account and keep its content."
And I should not see "Delete the account and its content."

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this case has the same problem as below, we only test with "administer users" permission but not with "cancel own.." and "select method for cancelling..".

Also we only cover the absence of "Delete the account and its content." but we miss to also cover the absence of "Delete the account and make its content belong to the %anonymous-name user."

(also see below about the fragility of testing the absence of something)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we can cover two "should not see" in one go if we only specify a partial phrase "Delete the account".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed

/** @var \Drupal\user\Entity\User $user */
$user = user_load_by_name($user_name);
if ($user === FALSE) {
throw new \InvalidArgumentException(sprintf('User with name %s could not be found.', $user_name));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the behat contexts in Drupal core I don't see "InvalidArgumentException" used, and only very few RuntimeException. Instead I see regular exception or ->assert*() calls.

To me, InvalidArgumentException and RuntimeException should indicate clear programming errors, but here the failure is conditional on the application state and data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I use Exception instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Delete user
2 participants