diff --git a/.env b/.env index 907caec3..47693076 100644 --- a/.env +++ b/.env @@ -17,7 +17,7 @@ COMPOSE_PROJECT_NAME=resop IMAGE_BUILD_TAG=dev -APP_NB_USERS=10 +APP_NB_USERS=15 APP_NB_AVAILABILITIES=3 APP_SLOT_INTERVAL="+2 hours" @@ -35,3 +35,7 @@ APP_SECRET=782bb8b0aeb47de4ea870794e79d82cd # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml DATABASE_URL=postgresql://resop:postgrespwd@postgres/resop?serverVersion=11&charset=utf8 ###< doctrine/doctrine-bundle ### + +###> symfony/mailer ### +MAILER_DSN=smtp://mailcatcher:25 +###< symfony/mailer ### diff --git a/.env.test b/.env.test index 47001204..69562c40 100644 --- a/.env.test +++ b/.env.test @@ -5,3 +5,4 @@ SYMFONY_DEPRECATIONS_HELPER=disabled DATABASE_URL=postgresql://resop:postgrespwd@postgres/resop-test?serverVersion=11&charset=utf8 PANTHER_CHROME_ARGUMENTS="--headless --no-sandbox" PANTHER_APP_ENV=panther +APP_SLOT_INTERVAL="+2 hours" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51364301..1ceee53a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Get the version id: vars run: | - echo ::set-output name=BUILD_TAG::$(git describe --tags) + echo ::set-output name=BUILD_TAG::$(git describe --tags --always) echo ::set-output name=CI_COMMIT_REF_SLUG::${GITHUB_REF#refs/*/} - name: Pull existing Docker image diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e6dd74b..ed221c5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ env: APP_DEBUG: 0 DATABASE_SERVER_VERSION: 11 # update service "postgresql" if this value change PANTHER_CHROME_DRIVER_BINARY: /usr/bin/chromedriver + MAILER_DSN: smtp://localhost:25 jobs: php: @@ -32,6 +33,11 @@ jobs: - 5432:5432 # needed because the postgres container does not provide a healthcheck options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + mailcatcher: + image: tophfr/mailcatcher:0.6.5 + ports: + - 1080:80 + - 25:25 steps: - name: set DATABASE_URL environment variable @@ -72,7 +78,7 @@ jobs: if: always() - name: Install composer dependencies - run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader + run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Cache node_modules uses: actions/cache@v1 @@ -133,6 +139,7 @@ jobs: - name: Run Behat tests run: | bin/post-install-test.sh + sed -i 's#http://mailcatcher#http://localhost:1080#' behat.yml.dist vendor/bin/behat --format=progress --out=std --format=junit --out=var/behat --tags '~@javascript' if: always() diff --git a/Makefile b/Makefile index 351d8138..67690faf 100644 --- a/Makefile +++ b/Makefile @@ -35,14 +35,13 @@ build-prod: docker build -t resop:latest -f docker/php-flex/Dockerfile . start-db: - $(DOCKER_COMPOSE_UP) traefik postgres adminer + $(DOCKER_COMPOSE_UP) traefik postgres adminer mailcatcher docker-compose run --rm wait -c postgres:5432 start-php: $(DOCKER_COMPOSE_UP_RECREATE) traefik nginx fpm docker-compose run --rm wait -c fpm:9000,nginx:80 - @echo -n "\nStack started with success:\nhttp://resop.vcap.me:7500/login => user1@resop.com : 01/01/1990" - @echo -n "\nhttp://resop.vcap.me:7500/organizations/login => DT75 : covid19\n" + @echo -n "\nStack started with success: http://resop.vcap.me:7500/\nuser102@resop.com : covid19\nadmin101@resop.com : covid19\nsuper_admin1@resop.com : covid19\n" start: init-db start-php @@ -130,6 +129,7 @@ test-coverage: bin/tools sh -c "COVERAGE=true vendor/bin/behat --format=progress" move-test-profiler: + @echo "You must set 'profiler: { collect: true }' in config/packages/test/web_profiler.yaml in order to use this command" bin/tools sh -c "rm -rf var/cache/dev/profiler && mkdir -p var/cache/dev && cp -R var/cache/test/profiler var/cache/dev/profiler" @echo "Done : http://resop.vcap.me:7500/_profiler/search?limit=10" diff --git a/assets/css/login.scss b/assets/css/login.scss index af0b7c8e..5d500a67 100644 --- a/assets/css/login.scss +++ b/assets/css/login.scss @@ -2,16 +2,6 @@ $xs: 380px; -@media (min-width: 360px) and (max-width: map-get($grid-breakpoints, "sm")) { - .form-inline { - .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - } -} - body.login { .navbar-and-body { background-image: url(../img/login-background.jpg); diff --git a/assets/js/_planning-missions.js b/assets/js/_planning-missions.js index cfd92dbf..0b1b019b 100644 --- a/assets/js/_planning-missions.js +++ b/assets/js/_planning-missions.js @@ -16,11 +16,11 @@ function setSlotMisssion(mission, $slot) { missionsText += ' '; // User part - let url = Routing.generate('app_user_availability_mission_modal', { id: mission.id }); + let url = Routing.generate('app_user_availability_mission_modal', { mission: mission.id }); if (window.location.pathname.indexOf('organizations') >= 0 && !!mission?.organization?.id) { // Organization part - url = Routing.generate('app_organization_mission_modal', { organization: mission.organization.id, id: mission.id }); + url = Routing.generate('app_organization_mission_modal', { organization: mission.organization.id, mission: mission.id }); } missionsText += $(` - + + + + + {% include 'organization/_help.html.twig' %} + + - {% include 'organization/_help.html.twig' %} + diff --git a/templates/organization/commissionable_asset/_list.html.twig b/templates/organization/commissionable_asset/_list.html.twig index 9d8cee29..a4d36b07 100644 --- a/templates/organization/commissionable_asset/_list.html.twig +++ b/templates/organization/commissionable_asset/_list.html.twig @@ -32,7 +32,7 @@ {% if showLinks is not defined or showLinks %} - diff --git a/templates/organization/commissionable_asset/_show.html.twig b/templates/organization/commissionable_asset/_show.html.twig index 9e228fc4..fbdf6127 100644 --- a/templates/organization/commissionable_asset/_show.html.twig +++ b/templates/organization/commissionable_asset/_show.html.twig @@ -1,5 +1,5 @@ -{% if app.user == asset.organization or app.user == asset.organization.parent %} - {{ 'action.edit' | trans }} +{% if is_granted('ROLE_PARENT_ORGANIZATION', asset.organization) %} + {{ 'action.edit' | trans }} {% endif %}

{{ asset }}

diff --git a/templates/organization/commissionable_asset/availability.html.twig b/templates/organization/commissionable_asset/availability.html.twig index 17958fae..162f3f75 100644 --- a/templates/organization/commissionable_asset/availability.html.twig +++ b/templates/organization/commissionable_asset/availability.html.twig @@ -9,6 +9,8 @@ {% endblock %} {% block body %} + {{ 'common.backToList' | trans }} +

{{ 'organization.asset.availabilities' | trans ({ '%asset%' : asset }) }}

{% include 'availability/_table.html.twig' with { availabilityType: 'assets', availabilityId: asset.id } %} diff --git a/templates/organization/commissionable_asset/form.html.twig b/templates/organization/commissionable_asset/form.html.twig index 8f07516d..4d5bd9c6 100644 --- a/templates/organization/commissionable_asset/form.html.twig +++ b/templates/organization/commissionable_asset/form.html.twig @@ -6,12 +6,14 @@ {% block body %} + {{ 'common.backToList' | trans }} + {% if asset.id %} {{ 'action.delete' | trans }} {% endif %} diff --git a/templates/organization/commissionable_asset/list.html.twig b/templates/organization/commissionable_asset/list.html.twig index 0a0c863b..ba9ff830 100644 --- a/templates/organization/commissionable_asset/list.html.twig +++ b/templates/organization/commissionable_asset/list.html.twig @@ -14,7 +14,7 @@
- + {{ 'organization.asset.add' | trans }}
diff --git a/templates/organization/commissionable_asset/preAdd.html.twig b/templates/organization/commissionable_asset/preAdd.html.twig index 4ec23805..5b431a92 100644 --- a/templates/organization/commissionable_asset/preAdd.html.twig +++ b/templates/organization/commissionable_asset/preAdd.html.twig @@ -1,7 +1,7 @@ {% extends 'organization/base.html.twig' %} {% block body %} - {{ form_start(form, { method: 'GET', action: path('app_organization_asset_add', {organization: organization.id | default(app.user.id)})}) }} + {{ form_start(form, { method: 'GET', action: path('app_organization_asset_add')}) }} {{ form_rest(form) }} {{ form_end(form) }} {% endblock %} diff --git a/templates/organization/edit.html.twig b/templates/organization/edit.html.twig index 5b2f8c10..79b21b85 100644 --- a/templates/organization/edit.html.twig +++ b/templates/organization/edit.html.twig @@ -4,7 +4,7 @@ {% set formAction = path('app_organization_new') %} {% set formTitle = 'organization.add' | trans %} {% else %} - {% set formAction = path('app_organization_edit', { object: organization.id }) %} + {% set formAction = path('app_organization_edit', { organizationToEdit: organization.id }) %} {% set formTitle = 'organization.edit' | trans %} {% endif %} diff --git a/templates/organization/home.html.twig b/templates/organization/home.html.twig index a934fa53..3edd8896 100644 --- a/templates/organization/home.html.twig +++ b/templates/organization/home.html.twig @@ -5,27 +5,27 @@ {% block body %} {{ include('misc/flash-messages.html.twig') }} -

{{ app.user }}

+

{{ organization }}

{{ 'calendar.week.current' | trans }} : {{ 'calendar.period' | trans ({ '%from%' : 'this week' | date('d/m/Y'), '%to%' : 'sunday this week' | date('d/m/Y') }) }}

- + {{ 'organization.userAvailabilityCurrentWeek' | trans }}

{{ 'calendar.week.next' | trans }} : {{ 'calendar.period' | trans ({ '%from%' : 'next week' | date('d/m/Y'), '%to%' : 'sunday next week' | date('d/m/Y') }) }}

- + {{ 'organization.userAvailabilityNextWeek' | trans }}


-

{{ 'organization.showUserList' | trans }}

-

{{ 'organization.showCommissionableAssets' | trans }}

+

{{ 'organization.showUserList' | trans }}

+

{{ 'organization.showCommissionableAssets' | trans }}

- {% if app.user.isParent() %} + {% if organization.isParent() %}

{{ 'organization.showAllUsersAvailability' | trans }}

{{ 'organization.forecast.title' | trans }}

diff --git a/templates/organization/list.html.twig b/templates/organization/list.html.twig index d81c5084..d8a11b4a 100644 --- a/templates/organization/list.html.twig +++ b/templates/organization/list.html.twig @@ -3,7 +3,7 @@ {% block title %}{{ 'organization.list' | trans }}{% endblock %} {% block body %} -

{{ app.user }}

+

{{ organization }}

@@ -19,14 +19,14 @@ - {% for organization in organizations %} + {% for child in organization.children %} - {{ organization.name }} + {{ child.name }} - {{ 'action.edit' | trans }} - {{ 'organization.userList' | trans }} - {{ 'organization.assetsList' | trans }} - {{ 'organization.userAvailabilities' | trans }} + {{ 'action.edit' | trans }} + {{ 'organization.userList' | trans }} + {{ 'organization.assetsList' | trans }} + {{ 'organization.userAvailabilities' | trans }} {% endfor %} diff --git a/templates/organization/login.html.twig b/templates/organization/login.html.twig deleted file mode 100644 index d814e45e..00000000 --- a/templates/organization/login.html.twig +++ /dev/null @@ -1,55 +0,0 @@ -{% extends 'organization/base.html.twig' %} - -{% block title %}{{ 'action.login' | trans }}{% endblock %} - -{% block body %} -
-
-
- {% if error %} -
- {{ error.messageKey|trans(error.messageData, 'security_organization') }} -
- {% endif %} - -

{{ 'action.login' | trans }}

- -
- - -
- -
- - -
- -
- -
- - - - -
-
-
- -{% endblock %} diff --git a/templates/organization/mission/_list.html.twig b/templates/organization/mission/_list.html.twig index c8219ca7..785040d3 100644 --- a/templates/organization/mission/_list.html.twig +++ b/templates/organization/mission/_list.html.twig @@ -58,7 +58,7 @@ {% endif %} {% if modalLinks is not defined or modalLinks %} - {% endif %} diff --git a/templates/organization/mission/_list_full.html.twig b/templates/organization/mission/_list_full.html.twig index 0d2cbb83..d7a90aa0 100644 --- a/templates/organization/mission/_list_full.html.twig +++ b/templates/organization/mission/_list_full.html.twig @@ -23,7 +23,7 @@ {{ mission.type.name | default('') }} - @@ -45,7 +45,7 @@ {{ user.organization.name }} - @@ -66,7 +66,7 @@ {{ asset.organization.name }} - diff --git a/templates/organization/mission/_show.html.twig b/templates/organization/mission/_show.html.twig index d6510371..d99b2cdd 100644 --- a/templates/organization/mission/_show.html.twig +++ b/templates/organization/mission/_show.html.twig @@ -1,4 +1,4 @@ -{% if app.user == mission.organization %} +{% if organization == mission.organization %} {{ 'action.edit' | trans }} {% endif %} diff --git a/templates/organization/planning/_availabilities_assets.html.twig b/templates/organization/planning/_availabilities_assets.html.twig index b233ccd2..8d7e5b85 100644 --- a/templates/organization/planning/_availabilities_assets.html.twig +++ b/templates/organization/planning/_availabilities_assets.html.twig @@ -11,7 +11,7 @@ {% endblock itemDataHeader %} {% block itemDataRowHeader %} - {% endblock itemDataRowHeader %} diff --git a/templates/organization/planning/_availabilities_users.html.twig b/templates/organization/planning/_availabilities_users.html.twig index 82c3dc72..a69e058f 100644 --- a/templates/organization/planning/_availabilities_users.html.twig +++ b/templates/organization/planning/_availabilities_users.html.twig @@ -14,7 +14,7 @@ {% endblock itemDataHeader %} {% block itemDataRowHeader %} - @@ -41,7 +41,7 @@ from: periodCalculator.from | date('Y-m-d\\T00:00:00'), to: periodCalculator.to | date_modify('- 1 minute') | date('Y-m-d\\T00:00:00'), organization: item.entity.organization.id, - userToAdd: item.entity.id + item: item.entity.id }) }}" title="{{ 'organization.asset.engage' | trans }}" > {{ 'organization.mission.title' | trans }} diff --git a/templates/organization/planning/_results.html.twig b/templates/organization/planning/_results.html.twig index 68dd456d..fff30a5a 100644 --- a/templates/organization/planning/_results.html.twig +++ b/templates/organization/planning/_results.html.twig @@ -1,4 +1,4 @@ -{% set displayActions = app.user.parent is empty %} +{% set displayActions = organization.parent is empty %}
diff --git a/templates/organization/search.html.twig b/templates/organization/search.html.twig index 87cd27aa..e7c9e885 100644 --- a/templates/organization/search.html.twig +++ b/templates/organization/search.html.twig @@ -15,11 +15,11 @@

Bénévoles

{% if users|length %} - {% include 'organization/user/_list.html.twig' with {organization: app.user} %} + {% include 'organization/user/_list.html.twig' %} {% else %}

{{ 'organization.search.noUsers' | trans }}

{% endif %} -

Afficher la liste de mes bénévoles inscrits

+

Afficher la liste de mes bénévoles inscrits


Véhicules

@@ -28,5 +28,5 @@ {% else %}

{{ 'organization.search.noAssets' | trans }}

{% endif %} -

Afficher la liste de mes véhicules

+

Afficher la liste de mes véhicules

{% endblock %} diff --git a/templates/organization/user/_list.html.twig b/templates/organization/user/_list.html.twig index 385439b0..39916590 100644 --- a/templates/organization/user/_list.html.twig +++ b/templates/organization/user/_list.html.twig @@ -38,9 +38,14 @@ {% if showLinks is not defined or showLinks %} - + {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} + + {{ 'action.impersonate' | trans }} + + {% endif %} {% endif %} diff --git a/templates/organization/user/_show.html.twig b/templates/organization/user/_show.html.twig index 9f14a4e1..27e4c44f 100644 --- a/templates/organization/user/_show.html.twig +++ b/templates/organization/user/_show.html.twig @@ -1,5 +1,15 @@ -{% if app.user == user.organization or app.user == user.organization.parent %} - {{ 'action.edit' | trans }} +{% if is_granted('ROLE_PARENT_ORGANIZATION', user.organization) %} + {{ 'action.edit' | trans }} +{% endif %} + +{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} + {{ 'action.impersonate' | trans }} +{% endif %} + +{% if is_granted('ROLE_PARENT_ORGANIZATION', user.organization) and not user.organization.admins.contains(user) %} + + {{ 'action.promote' | trans({ '%organization%': user.organization.name, '%user%': user.fullName }) }} + {% endif %}

{{ user }}

@@ -26,5 +36,33 @@ {% endfor %} +{% if is_granted('ROLE_PARENT_ORGANIZATION', user.organization) and user.managedOrganizations|length %} +
{{ 'organization.isAdminOf'|trans }}
+
+ + + + + + + + + {% for managedOrganization in user.managedOrganizations %} + + + + + {% endfor %} + +
{{ 'common.name' | trans }}
{{ managedOrganization }} + {% if app.user != user %} + + {{ 'action.revoke' | trans({ '%organization%': managedOrganization.name, '%user%': user.fullName }) }} + + {% endif %} +
+
+{% endif %} +
{{ 'organization.mission.listTitle'|trans }}
{% include 'organization/mission/_list.html.twig' with {missions: user.missions, modalLinks: true} %} diff --git a/templates/organization/user/edit.html.twig b/templates/organization/user/edit.html.twig index d9212767..094bfff7 100644 --- a/templates/organization/user/edit.html.twig +++ b/templates/organization/user/edit.html.twig @@ -6,15 +6,20 @@ {% endblock %} {% block body %} + {{ 'common.backToList' | trans }} +

{{ 'organization.editUserProfile' | trans }}

- {{ 'action.delete' | trans }} + +
{% include '/user/_user_form.html.twig' %} {% endblock %} diff --git a/templates/organization/user/list.html.twig b/templates/organization/user/list.html.twig index cf6013de..95ef5b16 100644 --- a/templates/organization/user/list.html.twig +++ b/templates/organization/user/list.html.twig @@ -12,5 +12,45 @@ {{ form(organization_selector_form) }} +
+ + + + + + + + + {% for user in organization.admins %} + + + + + {% else %} + + + + {% endfor %} + +
{{ 'user.admin' | trans }}
{{ user }} + + {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} + {{ 'action.impersonate' | trans }} + {% endif %} + {% if is_granted('ROLE_PARENT_ORGANIZATION', organization) and app.user != user %} + + {{ 'action.revoke' | trans({ '%organization%': organization.name, '%user%': user.fullName }) }} + + {% endif %} +
{{ 'message.noAvailableData' | trans }}
+
+ +

+ {{ 'organization.addAdmin' | trans }}
+ {{ 'organization.addAdminOtherOrganization' | trans }} +

+ {{ include('organization/user/_list.html.twig') }} {% endblock %} diff --git a/templates/reset_password/check_email.html.twig b/templates/reset_password/check_email.html.twig new file mode 100644 index 00000000..2f444222 --- /dev/null +++ b/templates/reset_password/check_email.html.twig @@ -0,0 +1,14 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ 'user.passwordForgotten.title' | trans }}{% endblock %} + +{% block body %} + +

{{ 'user.passwordForgotten.title' | trans }}

+ +

+ {{ 'user.passwordForgotten.emailCheck' | trans({'%hours%': tokenLifetime|date('g')}) }} +

+ {{ 'user.passwordForgotten.emailCheckAgain' | trans({'%link%': path('app_forgot_password_request')}) | raw }} +

+{% endblock %} diff --git a/templates/reset_password/email.html.twig b/templates/reset_password/email.html.twig new file mode 100644 index 00000000..35645016 --- /dev/null +++ b/templates/reset_password/email.html.twig @@ -0,0 +1,11 @@ +{% extends 'mailer/_template.html.twig' %} + +{% block content %} + {% import 'mailer/_macro.html.twig' as template %} + +

+ {{ 'user.resetPassword.emailText'|trans({'%hours%': tokenLifetime|date('g')}) }} +

+ + {{ template.button(url('app_reset_password', {token: resetToken.token}), 'user.resetPassword.button'|trans, 'reset-password') }} +{% endblock %} diff --git a/templates/reset_password/request.html.twig b/templates/reset_password/request.html.twig new file mode 100644 index 00000000..a5f79894 --- /dev/null +++ b/templates/reset_password/request.html.twig @@ -0,0 +1,21 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ 'user.passwordForgotten.title' | trans }}{% endblock %} + +{% block body %} + +

{{ 'user.passwordForgotten.title' | trans }}

+ +

{{ 'user.passwordForgotten.request' | trans }}

+ + {{ form_start(form) }} + + {% for error in app.flashes('error') %} + + {% endfor %} + + {{ form_row(form.emailAddress) }} + + + {{ form_end(form) }} +{% endblock %} diff --git a/templates/reset_password/reset.html.twig b/templates/reset_password/reset.html.twig new file mode 100644 index 00000000..bf447028 --- /dev/null +++ b/templates/reset_password/reset.html.twig @@ -0,0 +1,14 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ 'user.passwordForgotten.title' | trans }}{% endblock %} + +{% block body %} + +

{{ 'user.passwordForgotten.title' | trans }}

+ + {{ form_start(resetForm) }} + {{ form_row(resetForm.plainPassword) }} + + + {{ form_end(resetForm) }} +{% endblock %} diff --git a/templates/user/account-form.html.twig b/templates/user/account-form.html.twig index aa03b3dd..711c46c2 100644 --- a/templates/user/account-form.html.twig +++ b/templates/user/account-form.html.twig @@ -1,6 +1,6 @@ {% extends 'base.html.twig' %} -{% set actionName = (user is defined and user.id is not null) ? 'Modification' : 'Création' %} +{% set actionName = (user is defined and user.id is not null) ? 'Modification' : 'Création' %} {% block title %}{{ 'user.accountAction' | trans({ '%action%' : actionName }) }}{% endblock %} {% block javascripts %} @@ -9,6 +9,10 @@ {% endblock %} {% block body %} + {% if user is defined%} + {{ 'user.editMyPassword'|trans }} + {% endif %} +

{{ 'user.accountAction' | trans({ '%action%' : actionName }) }}

{% if user is not defined or user.id is not defined or user.id is null %} diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig index 5d23637d..8b016f8c 100644 --- a/templates/user/index.html.twig +++ b/templates/user/index.html.twig @@ -10,8 +10,6 @@ {% block body %}
- {{ include('misc/flash-messages.html.twig') }} -

{{ 'user.welcome'|trans }} {{ app.user.fullName }}

{{ app.user.organization }}

@@ -24,6 +22,14 @@

+ + {% if app.user.managedOrganizations|length and app.user.password is empty %} +

+ {{ 'user.passwordRequired'|trans }} +

+ {% endif %} + + {{ include('misc/flash-messages.html.twig') }}
diff --git a/templates/user/login.html.twig b/templates/user/login.html.twig index e35247ce..67dd73a6 100644 --- a/templates/user/login.html.twig +++ b/templates/user/login.html.twig @@ -16,13 +16,47 @@

{{ 'action.login' | trans }}

+ {{ include('misc/flash-messages.html.twig', {class: 'small'}) }} + {% if error %}
{{ error.messageKey|trans(error.messageData, 'security') }}
{% endif %} {{ form_start(loginForm) }} {{ form_row(loginForm.identifier) }} - {{ form_row(loginForm.birthday) }} + +
+
+
+ +
+
+
+ {{ form_errors(loginForm.birthday) }} + {{ form_widget(loginForm.birthday) }} +
+
+
+
+
+ +
+
+
+ {{ form_errors(loginForm.password) }} + {{ form_widget(loginForm.password) }} + + + {{ 'user.passwordForgotten.title' | trans }} + +
+
+
+
diff --git a/templates/user/password-form.html.twig b/templates/user/password-form.html.twig new file mode 100644 index 00000000..8fde091d --- /dev/null +++ b/templates/user/password-form.html.twig @@ -0,0 +1,27 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ 'user.passwordAction' | trans }}{% endblock %} + +{% block body %} +

{{ 'user.passwordAction' | trans }}

+ + {{ form_start(form) }} + {{ form_errors(form) }} + +
+ {% if form.currentPassword is defined %} +
+ {{ form_row(form.currentPassword) }} +
+ {% endif %} +
+ {{ form_row(form.plainPassword) }} +
+
+ +
+ +
+ + {{ form_end(form) }} +{% endblock %} diff --git a/tests/Behat/MailsContext.php b/tests/Behat/MailsContext.php new file mode 100644 index 00000000..e5111616 --- /dev/null +++ b/tests/Behat/MailsContext.php @@ -0,0 +1,51 @@ +getCrawler($this->getCurrentMessage()); + $link = $crawler->filter($selector); + + if (!$link->count()) { + throw new \RuntimeException("Unable to find the $selector link in mail"); + } + + if (empty($link->attr('href'))) { + throw new \RuntimeException("The $selector link does not have any href"); + } + + $this->getMinkContext()->visitPath((string) $link->attr('href')); + } + + private function getCrawler(Message $message): Crawler + { + if (!$message->isMultipart() || !$message->hasPart('text/html')) { + throw new \RuntimeException(sprintf('The current message has no html part.')); + } + + return new Crawler($message->getPart('text/html')->getContent()); + } + + private function getCurrentMessage(): Message + { + if (null === $this->currentMessage) { + throw new \RuntimeException('No message selected'); + } + + return $this->currentMessage; + } +} diff --git a/tests/Behat/MinkContextTrait.php b/tests/Behat/MinkContextTrait.php new file mode 100644 index 00000000..a7f556c2 --- /dev/null +++ b/tests/Behat/MinkContextTrait.php @@ -0,0 +1,39 @@ +getEnvironment(); + $minkContext = $environment->getContext(MinkContext::class); + + if (!$minkContext instanceof MinkContext) { + throw new \RuntimeException('Invalid mink context'); + } + + $this->minkContext = $minkContext; + } + + private function getMinkContext(): MinkContext + { + if (null === $this->minkContext) { + throw new \RuntimeException('Invalid mink context value'); + } + + return $this->minkContext; + } +} diff --git a/tests/Behat/ResetPasswordContext.php b/tests/Behat/ResetPasswordContext.php new file mode 100644 index 00000000..c0e19cff --- /dev/null +++ b/tests/Behat/ResetPasswordContext.php @@ -0,0 +1,38 @@ +router = $router; + $this->userProvider = $userProvider; + $this->resetPasswordHelper = $resetPasswordHelper; + } + + /** + * @When I go to the reset password page of :username + */ + public function generateToken(string $username): void + { + $this->visitPath( + $this->router->generate('app_reset_password', [ + 'token' => $this->resetPasswordHelper->generateResetToken( + $this->userProvider->loadUserByUsername($username) + )->getToken(), + ]) + ); + } +} diff --git a/tests/Behat/SecurityContext.php b/tests/Behat/SecurityContext.php index bc7cf07e..496c6ef3 100644 --- a/tests/Behat/SecurityContext.php +++ b/tests/Behat/SecurityContext.php @@ -4,18 +4,11 @@ namespace App\Tests\Behat; -use App\Entity\Organization; -use App\Entity\User; -use App\Repository\OrganizationRepository; use App\Repository\UserRepository; -use Behat\Behat\Context\Environment\InitializedContextEnvironment; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Mink\Driver\BrowserKitDriver; use Behat\Mink\Exception\ExpectationException; -use Behat\MinkExtension\Context\MinkContext; use Behat\MinkExtension\Context\RawMinkContext; use PantherExtension\Driver\PantherDriver; -use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; @@ -24,43 +17,25 @@ final class SecurityContext extends RawMinkContext { + use MinkContextTrait; + private UserRepository $userRepository; - private OrganizationRepository $organizationRepository; private SessionInterface $session; - private MinkContext $minkContext; - public function __construct(UserRepository $userRepository, OrganizationRepository $organizationRepository, SessionInterface $session) + public function __construct(UserRepository $userRepository, SessionInterface $session) { $this->userRepository = $userRepository; - $this->organizationRepository = $organizationRepository; $this->session = $session; } - /** - * @BeforeScenario - */ - public function gatherContext(BeforeScenarioScope $scope): void - { - /** @var InitializedContextEnvironment $environment */ - $environment = $scope->getEnvironment(); - /** @var MinkContext $minkContext */ - $minkContext = $environment->getContext(MinkContext::class); - $this->minkContext = $minkContext; - } - /** * @Given I am authenticated as :username */ - public function login(string $username, UserLoaderInterface $repository = null): void + public function login(string $username): void { - if ($repository) { - $user = $repository->loadUserByUsername($username); - } elseif (!$user = $this->userRepository->loadUserByUsername($username)) { - $user = $this->organizationRepository->loadUserByUsername($username); - } - + $user = $this->userRepository->loadUserByUsername($username); if (!$user) { - throw new UsernameNotFoundException(\sprintf('%s is not a valid User or Organization.', $username)); + throw new UsernameNotFoundException(\sprintf('%s is not a valid User.', $username)); } /** @var BrowserKitDriver|PantherDriver $driver */ @@ -72,10 +47,9 @@ public function login(string $username, UserLoaderInterface $repository = null): return; } - $firewall = $user instanceof Organization ? 'organizations' : 'main'; $this->session->set( - "_security_$firewall", - serialize(new UsernamePasswordToken($user, null, $firewall, $user->getRoles())) + '_security_main', + serialize(new UsernamePasswordToken($user, null, 'main', $user->getRoles())) ); $this->session->save(); @@ -87,33 +61,15 @@ public function login(string $username, UserLoaderInterface $repository = null): */ private function loginForPanther(UserInterface $user): void { + $minkContext = $this->getMinkContext(); try { - if ($user instanceof User) { - $this->loginUserForPantherDriver($user); - } - - if ($user instanceof Organization) { - $this->loginOrganizationForPantherDriver($user); - } + $minkContext->visit('/login'); + $minkContext->fillField('user_login[identifier]', $user->getUsername()); + $minkContext->fillField('user_login[password]', 'covid19'); + $minkContext->pressButton('Je me connecte'); + $minkContext->assertPageAddress('/'); } catch (\Exception $exception) { throw new ExpectationException(sprintf('Impossible to connect user: %s', $exception->getMessage()), $this->getSession(), $exception); } } - - private function loginUserForPantherDriver(User $user): void - { - $this->minkContext->visit('/login'); - $this->minkContext->fillField('user_login[identifier]', $user->getIdentificationNumber()); - $this->minkContext->pressButton('Je me connecte'); - $this->minkContext->assertPageAddress('/'); - } - - private function loginOrganizationForPantherDriver(Organization $user): void - { - $this->minkContext->visit('/organizations/login'); - $this->minkContext->selectOption('identifier', $user->getUsername()); - $this->minkContext->fillField('password', 'covid19'); - $this->minkContext->pressButton('Je me connecte'); - $this->minkContext->assertPageAddress('/organizations/'.$user->getId()); - } } diff --git a/tests/Behat/UserPlanningContext.php b/tests/Behat/UserPlanningContext.php index 12b03441..094f4413 100644 --- a/tests/Behat/UserPlanningContext.php +++ b/tests/Behat/UserPlanningContext.php @@ -4,12 +4,20 @@ namespace App\Tests\Behat; +use App\Domain\DatePeriodCalculator; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Exception\ExpectationException; use Behat\MinkExtension\Context\RawMinkContext; final class UserPlanningContext extends RawMinkContext { + private string $slotInterval; + + public function __construct(string $slotInterval) + { + $this->slotInterval = $slotInterval; + } + /** * @When /^I (?P(?:check|uncheck)) "(?P