Skip to content

Commit

Permalink
Disallow the edition of profiles managed by LDAP
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed Aug 4, 2023
1 parent 8e47428 commit 257917e
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 81 deletions.
24 changes: 22 additions & 2 deletions src/Controller/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use App\Repository\UserRepository;
use App\Utils\ConstraintErrorsFormatter;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
Expand All @@ -20,13 +21,16 @@
class ProfileController extends BaseController
{
#[Route('/profile', name: 'profile', methods: ['GET', 'HEAD'])]
public function edit(): Response
{
public function edit(
#[Autowire(env: 'bool:LDAP_ENABLED')]
string $ldapEnabled,
): Response {
/** @var \App\Entity\User $user */
$user = $this->getUser();
return $this->render('profile/edit.html.twig', [
'name' => $user->getName(),
'email' => $user->getEmail(),
'managedByLdap' => $ldapEnabled && $user->getAuthType() === 'ldap',
]);
}

Expand All @@ -38,6 +42,8 @@ public function update(
ValidatorInterface $validator,
RequestStack $requestStack,
TranslatorInterface $translator,
#[Autowire(env: 'bool:LDAP_ENABLED')]
string $ldapEnabled,
): Response {
/** @var \App\Entity\User $user */
$user = $this->getUser();
Expand All @@ -60,10 +66,22 @@ public function update(
/** @var string $csrfToken */
$csrfToken = $request->request->get('_csrf_token', '');

$managedByLdap = $ldapEnabled && $user->getAuthType() === 'ldap';

if ($managedByLdap) {
return $this->renderBadRequest('profile/edit.html.twig', [
'name' => $name,
'email' => $email,
'managedByLdap' => $managedByLdap,
'error' => $translator->trans('user.ldap.cannot_update_profile', [], 'errors'),
]);
}

if (!$this->isCsrfTokenValid('update profile', $csrfToken)) {
return $this->renderBadRequest('profile/edit.html.twig', [
'name' => $name,
'email' => $email,
'managedByLdap' => $managedByLdap,
'error' => $translator->trans('csrf.invalid', [], 'errors'),
]);
}
Expand All @@ -73,6 +91,7 @@ public function update(
return $this->renderBadRequest('profile/edit.html.twig', [
'name' => $name,
'email' => $email,
'managedByLdap' => $managedByLdap,
'errors' => [
'password' => $translator->trans('user.password.dont_match', [], 'errors'),
],
Expand All @@ -92,6 +111,7 @@ public function update(
return $this->renderBadRequest('profile/edit.html.twig', [
'name' => $name,
'email' => $email,
'managedByLdap' => $managedByLdap,
'errors' => ConstraintErrorsFormatter::format($errors),
]);
}
Expand Down
175 changes: 96 additions & 79 deletions templates/profile/edit.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,21 @@
{{ 'profile.note' | trans }}
</p>

{{ include('alerts/_alert.html.twig', {
type: 'info',
title: 'profile.ldap.information' | trans,
message: 'profile.ldap.managed' | trans,
}, with_context = false) }}

<div class="flow flow--small">
<label for="name">
{{ 'users.name' | trans }}
<span class="text--secondary">
{{ 'forms.optional_max_chars' | trans({ number: 100 }) }}
</span>

{% if not managedByLdap %}
<span class="text--secondary">
{{ 'forms.optional_max_chars' | trans({ number: 100 }) }}
</span>
{% endif %}
</label>

{% if errors.name is defined %}
Expand All @@ -55,6 +64,7 @@
aria-invalid="true"
aria-errormessage="name-error"
{% endif %}
{{ managedByLdap ? 'disabled' }}
/>
</div>

Expand All @@ -81,91 +91,98 @@
aria-invalid="true"
aria-errormessage="email-error"
{% endif %}
{{ managedByLdap ? 'disabled' }}
/>
</div>

<fieldset class="flow">
<legend>{{ 'users.password' | trans }}</legend>
{% if not managedByLdap %}
<fieldset class="flow">
<legend>{{ 'users.password' | trans }}</legend>

<p class="form__caption">
{{ 'profile.leave_password' | trans }}
</p>

<div class="flow flow--small">
<label for="current-password">
{{ 'profile.current_password' | trans }}
</label>

{% if errors.password is defined %}
<p class="form__error" role="alert" id="current-password-error">
<span class="sr-only">{{ 'forms.error' | trans }}</span>
{{ errors.password }}
</p>
{% endif %}
<p class="form__caption">
{{ 'profile.leave_password' | trans }}
</p>

<div class="input-container" data-controller="password">
<input
type="password"
id="current-password"
name="currentPassword"
autocomplete="current-password"
data-password-target="input"
{% if errors.password is defined %}
aria-invalid="true"
aria-errormessage="current-password-error"
{% endif %}
/>

<button
type="button"
role="switch"
data-action="password#toggle"
data-password-target="button"
>
{{ icon('eye') }}
{{ icon('eye-slash') }}
<span class="sr-only">
{{ 'forms.show_password' | trans }}
</span>
</button>
<div class="flow flow--small">
<label for="current-password">
{{ 'profile.current_password' | trans }}
</label>

{% if errors.password is defined %}
<p class="form__error" role="alert" id="current-password-error">
<span class="sr-only">{{ 'forms.error' | trans }}</span>
{{ errors.password }}
</p>
{% endif %}

<div class="input-container" data-controller="password">
<input
type="password"
id="current-password"
name="currentPassword"
autocomplete="current-password"
data-password-target="input"
{% if errors.password is defined %}
aria-invalid="true"
aria-errormessage="current-password-error"
{% endif %}
/>

<button
type="button"
role="switch"
data-action="password#toggle"
data-password-target="button"
>
{{ icon('eye') }}
{{ icon('eye-slash') }}
<span class="sr-only">
{{ 'forms.show_password' | trans }}
</span>
</button>
</div>
</div>
</div>

<div class="flow flow--small">
<label for="new-password">
{{ 'profile.new_password' | trans }}
</label>

<div class="input-container" data-controller="password">
<input
type="password"
id="new-password"
name="newPassword"
autocomplete="new-password"
data-password-target="input"
/>

<button
type="button"
role="switch"
data-action="password#toggle"
data-password-target="button"
>
{{ icon('eye') }}
{{ icon('eye-slash') }}
<span class="sr-only">
{{ 'forms.show_password' | trans }}
</span>
</button>
<div class="flow flow--small">
<label for="new-password">
{{ 'profile.new_password' | trans }}
</label>

<div class="input-container" data-controller="password">
<input
type="password"
id="new-password"
name="newPassword"
autocomplete="new-password"
data-password-target="input"
/>

<button
type="button"
role="switch"
data-action="password#toggle"
data-password-target="button"
>
{{ icon('eye') }}
{{ icon('eye-slash') }}
<span class="sr-only">
{{ 'forms.show_password' | trans }}
</span>
</button>
</div>
</div>
</fieldset>

<div class="form__actions">
<button
id="form-update-profile-submit"
class="button--primary"
type="submit"
>
{{ 'forms.save_changes' | trans }}
</button>
</div>
</fieldset>

<div class="form__actions">
<button id="form-update-profile-submit" class="button--primary" type="submit">
{{ 'forms.save_changes' | trans }}
</button>
</div>
{% endif %}
</form>
</main>
{% endblock %}
29 changes: 29 additions & 0 deletions tests/Controller/ProfileControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,35 @@ public function testPostUpdateFailsIfCurrentPasswordIsInvalid(): void
$this->assertFalse($passwordHasher->isPasswordValid($user->object(), $newPassword));
}

public function testPostUpdateFailsIfManagedByLdap(): void
{
$client = static::createClient();
$initialName = Factory::faker()->unique()->userName();
$newName = Factory::faker()->unique()->userName();
$initialEmail = Factory::faker()->unique()->email();
$newEmail = Factory::faker()->unique()->email();
$user = UserFactory::createOne([
'name' => $initialName,
'email' => $initialEmail,
'ldapIdentifier' => 'charlie',
]);
$client->loginUser($user->object());

$client->request('POST', '/profile', [
'_csrf_token' => $this->generateCsrfToken($client, 'update profile'),
'name' => $newName,
'email' => $newEmail,
]);

$this->assertSelectorTextContains(
'[data-test="alert-error"]',
'You can’t update your profile because it’s managed by LDAP.',
);
$user->refresh();
$this->assertSame($initialName, $user->getName());
$this->assertSame($initialEmail, $user->getEmail());
}

public function testPostUpdateFailsIfCsrfTokenIsInvalid(): void
{
$client = static::createClient();
Expand Down
1 change: 1 addition & 0 deletions translations/errors+intl-icu.en_GB.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ user.email.invalid: 'Enter a valid email address.'
user.email.required: 'Enter an email address.'
user.language.invalid: 'Select a language from the list.'
user.language.required: 'Select a language.'
user.ldap.cannot_update_profile: 'You can’t update your profile because it’s managed by LDAP.'
user.name.max_chars: 'Enter a name of less than {limit} characters.'
user.password.dont_match: 'The password does not match, please try with a different one.'
1 change: 1 addition & 0 deletions translations/errors+intl-icu.fr_FR.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ user.email.invalid: 'Saisissez une adresse email valide.'
user.email.required: 'Saisissez une adresse email.'
user.language.invalid: 'Sélectionnez une langue de la liste.'
user.language.required: 'Sélectionnez une langue.'
user.ldap.cannot_update_profile: 'Vous ne pouvez pas modifier votre profil car il est géré avec LDAP.'
user.name.max_chars: 'Saisissez un nom de moins de {limit} caractères.'
user.password.dont_match: 'Le mot de passe ne correspond pas, veuillez essayer avec un différent.'
2 changes: 2 additions & 0 deletions translations/messages+intl-icu.en_GB.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ organizations.new.title: 'New organization'
organizations.parent_orga: 'Parent organization'
preferences.title: Preferences
profile.current_password: 'Current password'
profile.ldap.information: Information
profile.ldap.managed: 'You can’t change your profile because your account is managed by LDAP.'
profile.leave_password: 'Leave these fields blank to keep your current password.'
profile.new_password: 'New password'
profile.note: 'Note: the information of your profile can be visible to the other users.'
Expand Down
2 changes: 2 additions & 0 deletions translations/messages+intl-icu.fr_FR.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ organizations.new.title: 'Nouvelle organisation'
organizations.parent_orga: 'Organisation parente'
preferences.title: Préférences
profile.current_password: 'Mot de passe actuel'
profile.ldap.information: Information
profile.ldap.managed: 'Vous ne pouvez pas modifier votre profil car votre compte est géré avec LDAP.'
profile.leave_password: 'Laissez ces champs vides pour conserver votre mot de passe actuel.'
profile.new_password: 'Nouveau mot de passe'
profile.note: "Note\_: les informations de votre profil peuvent être visibles par les autres utilisateurs."
Expand Down

0 comments on commit 257917e

Please sign in to comment.