Пример #1
0
    def setUpTestData(cls):
        cls.entry1 = Journal.objects.create(action="connect_aidant",
                                            initiator="ABC")
        cls.aidant_thierry = AidantFactory(
            username="******",
            email="*****@*****.**",
            first_name="Thierry",
            last_name="Martin",
            organisation=OrganisationFactory(name="Commune de Vernon"),
        )
        cls.usager_ned = UsagerFactory(given_name="Ned",
                                       family_name="Flanders")

        cls.first_mandat = MandatFactory(
            organisation=cls.aidant_thierry.organisation,
            usager=cls.usager_ned,
            expiration_date=timezone.now() + timedelta(days=6),
        )
        cls.first_autorisation = AutorisationFactory(
            mandat=cls.first_mandat,
            demarche="Revenus",
        )
        Journal.log_autorisation_creation(cls.first_autorisation,
                                          aidant=cls.aidant_thierry)

        cls.mandat_thierry_ned_365 = MandatFactory(
            organisation=cls.aidant_thierry.organisation,
            usager=cls.usager_ned,
            expiration_date=timezone.now() + timedelta(days=365),
        )
Пример #2
0
 def generate_log_entries(self, result, request):
     super().generate_log_entries(result, request)
     Journal.log_toitp_card_import(
         request.user,
         result.totals[RowResult.IMPORT_TYPE_NEW],
         result.totals[RowResult.IMPORT_TYPE_UPDATE],
     )
Пример #3
0
def confirm_autorisation_cancelation(request, usager_id, autorisation_id):
    aidant = request.user
    try:
        autorisation = aidant.get_active_autorisations_for_usager(
            usager_id).get(pk=autorisation_id)
    except Autorisation.DoesNotExist:
        django_messages.error(
            request, "Cette autorisation est introuvable ou inaccessible.")
        return redirect("espace_aidant_home")

    if request.method == "POST":
        form = request.POST

        if form:
            autorisation.revocation_date = timezone.now()
            autorisation.save(update_fields=["revocation_date"])

            Journal.log_autorisation_cancel(autorisation, aidant)

            return redirect(
                "autorisation_cancelation_success",
                usager_id=usager_id,
                autorisation_id=autorisation_id,
            )

    return render(
        request,
        "aidants_connect_web/mandat_auths_cancellation/"
        "confirm_autorisation_cancelation.html",
        {
            "aidant": aidant,
            "usager": aidant.get_usager(usager_id),
            "autorisation": autorisation,
        },
    )
Пример #4
0
def usagers_mandats_autorisations_cancel_confirm(
    request, usager_id, mandat_id, autorisation_id
):
    aidant = request.user

    usager = aidant.get_usager(usager_id)
    if not usager:
        django_messages.error(request, "Cet usager est introuvable ou inaccessible.")
        return redirect("espace_aidant_home")

    autorisation = usager.get_autorisation(mandat_id, autorisation_id)
    if not autorisation:
        django_messages.error(
            request, "Cette autorisation est introuvable ou inaccessible."
        )
        return redirect("espace_aidant_home")
    if autorisation.is_revoked:
        django_messages.error(request, "L'autorisation a été révoquée")
        return redirect("espace_aidant_home")
    if autorisation.is_expired:
        django_messages.error(request, "L'autorisation a déjà expiré")
        return redirect("espace_aidant_home")

    if request.method == "POST":
        form = request.POST

        if form:
            autorisation.revocation_date = timezone.now()
            autorisation.save(update_fields=["revocation_date"])

            Journal.log_autorisation_cancel(autorisation, aidant)

            django_messages.success(
                request, "L'autorisation a été révoquée avec succès !"
            )
            return redirect("usager_details", usager_id=usager.id)

        else:
            return render(
                request,
                "aidants_connect_web/usagers_mandats_autorisations_cancel_confirm.html",
                {
                    "aidant": aidant,
                    "usager": usager,
                    "autorisation": autorisation,
                    "error": "Erreur lors de l'annulation de l'autorisation.",
                },
            )

    return render(
        request,
        "aidants_connect_web/usagers_mandats_autorisations_cancel_confirm.html",
        {
            "aidant": aidant,
            "usager": usager,
            "autorisation": autorisation,
            "error": "Erreur lors de l'annulation de l'autorisation.",
        },
    )
def validate_aidant_carte_totp(request, aidant_id):
    responsable: Aidant = request.user
    aidant = get_object_or_404(Aidant, pk=aidant_id)
    if not responsable.can_see_aidant(aidant):
        raise Http404

    if not hasattr(aidant, "carte_totp"):
        django_messages.error(
            request,
            (
                "Impossible de trouver une carte Aidants Connect associée au compte de "
                f"{aidant.get_full_name()}."
                "Vous devez d’abord lier une carte à son compte."
            ),
        )
        return redirect(
            "espace_responsable_aidant",
            aidant_id=aidant.id,
        )

    if request.method == "POST":
        form = CarteTOTPValidationForm(request.POST)
    else:
        form = CarteTOTPValidationForm()

    if form.is_valid():
        token = form.cleaned_data["otp_token"]
        totp_device = TOTPDevice.objects.get(
            key=aidant.carte_totp.seed, user_id=aidant.id
        )
        valid = totp_device.verify_token(token)
        if valid:
            with transaction.atomic():
                totp_device.tolerance = 1
                totp_device.confirmed = True
                totp_device.save()
                Journal.log_card_validation(
                    responsable, aidant, aidant.carte_totp.serial_number
                )
            django_messages.success(
                request,
                (
                    "Tout s’est bien passé, le compte de "
                    f"{aidant.get_full_name()} est prêt !"
                ),
            )
            return redirect(
                "espace_responsable_aidant",
                aidant_id=aidant.id,
            )
        else:
            django_messages.error(request, "Ce code n’est pas valide.")

    return render(
        request,
        "aidants_connect_web/espace_responsable/validate-carte-totp.html",
        {"aidant": aidant, "organisation": organisation, "form": form},
    )
def associate_aidant_carte_totp(request, aidant_id):
    responsable: Aidant = request.user
    aidant = get_object_or_404(Aidant, pk=aidant_id)
    if not responsable.can_see_aidant(aidant):
        raise Http404

    if hasattr(aidant, "carte_totp"):
        django_messages.error(
            request,
            (
                f"Le compte de {aidant.get_full_name()} est déjà lié à une carte "
                "Aidants Connect. Vous devez d’abord retirer la carte de son compte "
                "avant de pouvoir en lier une nouvelle."
            ),
        )
        return redirect(
            "espace_responsable_aidant",
            aidant_id=aidant.id,
        )

    if request.method == "GET":
        form = CarteOTPSerialNumberForm()

    if request.method == "POST":
        form = CarteOTPSerialNumberForm(request.POST)
        if form.is_valid():
            serial_number = form.cleaned_data["serial_number"]
            try:
                carte_totp = CarteTOTP.objects.get(serial_number=serial_number)

                with transaction.atomic():
                    carte_totp.aidant = aidant
                    carte_totp.save()
                    totp_device = carte_totp.createTOTPDevice()
                    totp_device.save()
                    Journal.log_card_association(responsable, aidant, serial_number)

                return redirect(
                    "espace_responsable_validate_totp",
                    aidant_id=aidant.id,
                )
            except Exception:
                django_messages.error(
                    request,
                    "Une erreur s’est produite lors de la sauvegarde de la carte.",
                )
                # todo send exception to Sentry

    return render(
        request,
        "aidants_connect_web/espace_responsable/write-carte-totp-sn.html",
        {
            "aidant": aidant,
            "organisation": organisation,
            "responsable": responsable,
            "form": form,
        },
    )
Пример #7
0
def user_info(request):
    auth_header = request.META.get("HTTP_AUTHORIZATION")
    if not auth_header:
        log.info("403: Missing auth header")
        return HttpResponseForbidden()

    pattern = re.compile(r"^Bearer\s([A-Z-a-z-0-9-_/-]+)$")
    if not pattern.match(auth_header):
        log.info("Auth header has wrong format")
        return HttpResponseForbidden()

    auth_token = auth_header[7:]
    auth_token_hash = make_password(auth_token, settings.FC_AS_FI_HASH_SALT)
    try:
        connection = Connection.objects.get(access_token=auth_token_hash)
        if connection.is_expired:
            log.info("connection has expired at user_info")
            return render(request, "408.html", status=408)
    except ObjectDoesNotExist:
        log.info(
            "403: /user_info No connection corresponds to the access_token")
        log.info(auth_token)
        return HttpResponseForbidden()

    usager = model_to_dict(
        connection.usager,
        fields=[
            "birthcountry",
            "birthdate",
            "birthplace",
            "creation_date",
            "email",
            "family_name",
            "gender",
            "given_name",
            "preferred_username",
            "sub",
        ],
    )

    birthdate = usager["birthdate"]
    birthplace = usager["birthplace"]
    birthcountry = usager["birthcountry"]
    usager["birthplace"] = str(birthplace)
    usager["birthcountry"] = str(birthcountry)
    usager["birthdate"] = str(birthdate)

    Journal.log_autorisation_use(
        aidant=connection.aidant,
        usager=connection.usager,
        demarche=connection.demarche,
        access_token=connection.access_token,
        autorisation=connection.autorisation,
    )

    return JsonResponse(usager, safe=False)
Пример #8
0
def renew_mandat(request, usager_id):
    aidant: Aidant = request.user
    usager: Usager = aidant.get_usager(usager_id)

    if not usager:
        django_messages.error(request, "Cet usager est introuvable ou inaccessible.")
        return redirect("espace_aidant_home")

    form = MandatForm()

    if request.method == "GET":
        return render(
            request,
            "aidants_connect_web/new_mandat/renew_mandat.html",
            {"aidant": aidant, "form": form},
        )

    else:
        form = MandatForm(request.POST)

        if form.is_valid():
            data = form.cleaned_data
            access_token = make_password(token_urlsafe(64), settings.FC_AS_FI_HASH_SALT)
            connection = Connection.objects.create(
                aidant=aidant,
                organisation=aidant.organisation,
                connection_type="FS",
                access_token=access_token,
                usager=usager,
                demarches=data["demarche"],
                duree_keyword=data["duree"],
                mandat_is_remote=data["is_remote"],
            )
            duree = AuthorizationDurations.duration(connection.duree_keyword)
            Journal.log_init_renew_mandat(
                aidant=aidant,
                usager=usager,
                demarches=connection.demarches,
                duree=duree,
                is_remote_mandat=connection.mandat_is_remote,
                access_token=connection.access_token,
            )

            request.session["connection"] = connection.pk
            return redirect("new_mandat_recap")
        else:
            return render(
                request,
                "aidants_connect_web/new_mandat/renew_mandat.html",
                {"aidant": aidant, "form": form},
            )
Пример #9
0
    def test_log_autorisation_creation_complete(self):

        autorisation = AutorisationFactory(
            mandat=self.mandat_thierry_ned_365,
            demarche="logement",
        )
        Journal.log_autorisation_creation(autorisation, self.aidant_thierry)

        journal_entries = Journal.objects.all()
        self.assertEqual(len(journal_entries), 3)

        last_entry = journal_entries.last()
        self.assertEqual(last_entry.action, "create_autorisation")
        self.assertIn("Ned Flanders", last_entry.usager)
        self.assertEqual(last_entry.autorisation, autorisation.id)
Пример #10
0
 def test_logging_of_aidant_conection(self):
     entry = Journal.log_connection(aidant=self.aidant_thierry)
     self.assertEqual(len(Journal.objects.all()), 3)
     self.assertEqual(entry.action, "connect_aidant")
     self.assertEqual(
         entry.initiator,
         "Thierry Martin - Commune de Vernon - [email protected]")
Пример #11
0
    def test_a_create_attestation_journal_entry_can_be_created(self):
        demarches = ["transports", "logement"]
        expiration_date = timezone.now() + timedelta(days=6)
        entry = Journal.log_attestation_creation(
            aidant=self.aidant_thierry,
            usager=self.usager_ned,
            demarches=demarches,
            duree=6,
            is_remote_mandat=False,
            access_token="fjfgjfdkldlzlsmqqxxcn",
            attestation_hash=generate_attestation_hash(self.aidant_thierry,
                                                       self.usager_ned,
                                                       demarches,
                                                       expiration_date),
        )

        self.assertEqual(len(Journal.objects.all()), 3)
        self.assertEqual(entry.action, "create_attestation")

        attestation_string = ";".join([
            str(self.aidant_thierry.id),
            date.today().isoformat(),
            "logement,transports",
            expiration_date.date().isoformat(),
            str(self.aidant_thierry.organisation.id),
            generate_file_sha256_hash(settings.MANDAT_TEMPLATE_PATH),
            self.usager_ned.sub,
        ])
        self.assertTrue(
            validate_attestation_hash(attestation_string,
                                      entry.attestation_hash))
Пример #12
0
    def test_a_franceconnect_usager_journal_entry_can_be_created(self):
        entry = Journal.log_franceconnection_usager(
            aidant=self.aidant_thierry,
            usager=self.usager_ned,
        )

        self.assertEqual(len(Journal.objects.all()), 3)
        self.assertEqual(entry.action, "franceconnect_usager")
Пример #13
0
def remove_card_from_aidant(request, aidant_id):
    responsable: Aidant = request.user
    aidant = get_object_or_404(Aidant, pk=aidant_id)
    if not responsable.can_see_aidant(aidant):
        raise Http404

    if not aidant.has_a_carte_totp:
        return redirect(
            "espace_responsable_aidant",
            aidant_id=aidant.id,
        )

    form = RemoveCardFromAidantForm(request.POST)

    if not form.is_valid():
        raise Exception("Invalid form for card/aidant dissociation")

    data = form.cleaned_data
    reason = data.get("reason")
    if reason == "autre":
        reason = data.get("other_reason")
    sn = aidant.carte_totp.serial_number
    with transaction.atomic():
        carte = CarteTOTP.objects.get(serial_number=sn)
        try:
            device = TOTPDevice.objects.get(key=carte.seed, user=aidant)
            device.delete()
        except TOTPDevice.DoesNotExist:
            pass
        carte.aidant = None
        carte.save()
        Journal.log_card_dissociation(responsable, aidant, sn, reason)

    django_messages.success(
        request,
        (
            f"Tout s’est bien passé, la carte {sn} a été séparée du compte "
            f"de l’aidant {aidant.get_full_name()}."
        ),
    )

    return redirect(
        "espace_responsable_aidant",
        aidant_id=aidant.id,
    )
Пример #14
0
def get_user_info(connection: Connection) -> tuple:
    fc_base = settings.FC_AS_FS_BASE_URL
    fc_user_info = python_request.get(
        f"{fc_base}/userinfo?schema=openid",
        headers={"Authorization": f"Bearer {connection.access_token}"},
    )
    user_info = fc_user_info.json()

    if user_info.get("birthplace") == "":
        user_info["birthplace"] = None

    usager_sub = generate_sha256_hash(
        f"{user_info['sub']}{settings.FC_AS_FI_HASH_SALT}".encode())

    try:
        usager = Usager.objects.get(sub=usager_sub)

        if usager.email != user_info.get("email"):
            usager.email = user_info.get("email")
            usager.save()

            Journal.log_update_email_usager(aidant=connection.aidant,
                                            usager=usager)

        return usager, None

    except Usager.DoesNotExist:
        try:
            usager = Usager.objects.create(
                given_name=user_info.get("given_name"),
                family_name=user_info.get("family_name"),
                birthdate=user_info.get("birthdate"),
                gender=user_info.get("gender"),
                birthplace=user_info.get("birthplace"),
                birthcountry=user_info.get("birthcountry"),
                sub=usager_sub,
                email=user_info.get("email"),
            )
            return usager, None

        except IntegrityError as e:
            log.error("Error happened in Recap")
            log.error(e)
            return None, f"The FranceConnect ID is not complete: {e}"
Пример #15
0
    def __dissociate_from_aidant_post(self, request, object_id):
        try:
            object = CarteTOTP.objects.get(id=object_id)
            aidant = object.aidant
            if aidant is None:
                self.message_user(
                    request,
                    f"Aucun aidant n’est associé à la carte {object.serial_number}.",
                    messages.ERROR,
                )
                return HttpResponseRedirect(
                    reverse(
                        "otpadmin:aidants_connect_web_cartetotp_changelist"))

            totp_devices = TOTPDevice.objects.filter(user=aidant,
                                                     key=object.seed)
            for d in totp_devices:
                d.delete()
            object.aidant = None
            object.save()

            Journal.log_card_dissociation(request.user, aidant,
                                          object.serial_number, "Admin action")

            self.message_user(request, "Tout s'est bien passé.")
            return HttpResponseRedirect(
                reverse(
                    "otpadmin:aidants_connect_web_cartetotp_change",
                    kwargs={"object_id": object_id},
                ))
        except Exception:
            logger.exception(
                "An error occured while trying to dissociate an aidant"
                "from their carte TOTP")

            self.message_user(
                request,
                "Quelque chose s’est mal passé durant l'opération.",
                messages.ERROR,
            )

        return HttpResponseRedirect(
            reverse("otpadmin:aidants_connect_web_cartetotp_changelist"))
Пример #16
0
 def test_log_autorisation_use_complete(self):
     entry = Journal.log_autorisation_use(
         aidant=self.aidant_thierry,
         usager=self.usager_ned,
         demarche="transports",
         access_token="fjfgjfdkldlzlsmqqxxcn",
         autorisation=self.first_autorisation,
     )
     self.assertEqual(len(Journal.objects.all()), 3)
     self.assertEqual(entry.action, "use_autorisation")
     self.assertEqual(entry.demarche, "transports")
Пример #17
0
    def test_it_is_impossible_to_delete_an_existing_entry(self):
        entry = Journal.log_autorisation_use(
            aidant=self.aidant_thierry,
            usager=self.usager_ned,
            demarche="transports",
            access_token="fjfgjfdkldlzlsmqqxxcn",
            autorisation=self.first_autorisation,
        )

        self.assertRaises(NotImplementedError, entry.delete)
        self.assertEqual(
            Journal.objects.get(id=entry.id).demarche, "transports")
Пример #18
0
def activity_check(request):
    next_page = request.GET.get("next", settings.LOGIN_REDIRECT_URL)

    if not url_has_allowed_host_and_scheme(
            next_page, allowed_hosts={request.get_host()}, require_https=True):
        log.warning(
            "[Aidants Connect] an unsafe URL was used through the activity check"
        )
        return HttpResponseNotFound()

    aidant = request.user
    if request.method == "POST":
        form = OTPForm(aidant=aidant, data=request.POST)

        if form.is_valid():
            Journal.log_activity_check(aidant)
            return redirect(next_page)
    else:
        form = OTPForm(request.user)

    return render(request, "login/activity_check.html", {
        "form": form,
        "aidant": aidant
    })
Пример #19
0
def log_connection_on_login(sender, user: Aidant, request, **kwargs):
    Journal.log_connection(user)
Пример #20
0
    def test_updating_revoked_autorisation_for_same_organisation(self):
        # first session : creating the autorisation
        self.client.force_login(self.aidant_thierry)
        mandat_builder_1 = Connection.objects.create(
            usager=self.test_usager, demarches=["papiers"], duree_keyword="SHORT"
        )
        session = self.client.session
        session["connection"] = mandat_builder_1.id
        session.save()

        # trigger the mandat creation
        self.client.post(
            "/creation_mandat/recapitulatif/",
            data={"personal_data": True, "brief": True, "otp_token": "123456"},
        )

        self.assertEqual(Autorisation.objects.count(), 1)
        last_journal_entry = Journal.objects.last()
        self.assertEqual(last_journal_entry.action, "create_autorisation")

        # revoke
        self.client.post(
            "/creation_mandat/recapitulatif/",
            data={"personal_data": True, "brief": True, "otp_token": "123456"},
        )

        last_autorisation = Autorisation.objects.last()
        last_autorisation.revocation_date = timezone.now()
        last_autorisation.save(update_fields=["revocation_date"])

        Journal.log_autorisation_cancel(last_autorisation, self.aidant_thierry)

        # second session : 'updating' the autorisation
        self.client.force_login(self.aidant_thierry)
        mandat_builder_2 = Connection.objects.create(
            usager=self.test_usager,
            demarches=["papiers", "logement"],
            duree_keyword="LONG",
        )
        session = self.client.session
        session["connection"] = mandat_builder_2.id
        session.save()

        # trigger the mandat creation
        self.client.post(
            "/creation_mandat/recapitulatif/",
            data={"personal_data": True, "brief": True, "otp_token": "223456"},
        )

        self.assertEqual(Autorisation.objects.count(), 3)

        last_usager_organisation_papiers_autorisations = Autorisation.objects.filter(
            demarche="papiers",
            mandat__usager=self.test_usager,
            mandat__organisation=self.aidant_thierry.organisation,
        ).order_by("-mandat__creation_date")
        new_papiers_autorisation = last_usager_organisation_papiers_autorisations[0]
        old_papiers_autorisation = last_usager_organisation_papiers_autorisations[1]
        self.assertEqual(new_papiers_autorisation.duration_for_humans, 365)
        self.assertFalse(old_papiers_autorisation.is_expired)
        self.assertTrue(old_papiers_autorisation.is_revoked)

        last_journal_entries = Journal.objects.all().order_by("-id")
        self.assertEqual(last_journal_entries[0].action, "create_autorisation")
        self.assertEqual(last_journal_entries[0].demarche, "papiers")
        self.assertEqual(last_journal_entries[1].action, "create_autorisation")
        self.assertEqual(last_journal_entries[1].demarche, "logement")
        self.assertEqual(last_journal_entries[2].action, "create_attestation")
        self.assertEqual(last_journal_entries[2].demarche, "logement,papiers")

        self.assertEqual(
            len(self.aidant_thierry.get_active_demarches_for_usager(self.test_usager)),
            2,
        )
Пример #21
0
def on_login(sender, user, request, **kwargs):
    Journal.log_connection(user)
Пример #22
0
def fc_callback(request):
    fc_base = settings.FC_AS_FS_BASE_URL
    fc_callback_uri = f"{settings.FC_AS_FS_CALLBACK_URL}/callback"
    fc_callback_uri_logout = f"{settings.FC_AS_FS_CALLBACK_URL}/logout-callback"
    fc_id = settings.FC_AS_FS_ID
    fc_secret = settings.FC_AS_FS_SECRET
    state = request.GET.get("state")

    try:
        connection = Connection.objects.get(state=state)
    except Connection.DoesNotExist:
        log.info("FC as FS - This state does not seem to exist")
        log.info(state)
        return HttpResponseForbidden()

    if connection.is_expired:
        log.info("408: FC connection has expired.")
        return render(request, "408.html", status=408)

    code = request.GET.get("code")
    if not code:
        log.info("403: No code has been provided.")
        return HttpResponseForbidden()

    token_url = f"{fc_base}/token"
    payload = {
        "grant_type": "authorization_code",
        "redirect_uri": fc_callback_uri,
        "client_id": fc_id,
        "client_secret": fc_secret,
        "code": code,
    }
    headers = {"Accept": "application/json"}

    request_for_token = python_request.post(token_url,
                                            data=payload,
                                            headers=headers)
    content = request_for_token.json()
    connection.access_token = content.get("access_token")
    connection.save()
    fc_id_token = content.get("id_token")

    try:
        decoded_token = jwt.decode(
            fc_id_token,
            settings.FC_AS_FS_SECRET,
            audience=settings.FC_AS_FS_ID,
            algorithm="HS256",
        )
    except ExpiredSignatureError:
        log.info("403: token signature has expired.")
        return HttpResponseForbidden()

    if connection.nonce != decoded_token.get("nonce"):
        log.info("403: The nonce is different than the one expected.")
        return HttpResponseForbidden()

    if connection.is_expired:
        log.info("408: FC connection has expired.")
        return render(request, "408.html", status=408)

    usager, error = get_user_info(connection)
    if error:
        django_messages.error(request, error)
        return redirect("espace_aidant_home")

    connection.usager = usager
    connection.save()

    Journal.log_franceconnection_usager(
        aidant=connection.aidant,
        usager=connection.usager,
    )

    logout_base = f"{fc_base}/logout"
    logout_id_token = f"id_token_hint={fc_id_token}"
    logout_state = f"state={state}"
    logout_redirect = f"post_logout_redirect_uri={fc_callback_uri_logout}"
    logout_url = f"{logout_base}?{logout_id_token}&{logout_state}&{logout_redirect}"
    return redirect(logout_url)
Пример #23
0
 def test_log_autorisation_cancel_complete(self):
     entry = Journal.log_autorisation_cancel(
         autorisation=self.first_autorisation, aidant=self.aidant_thierry)
     self.assertEqual(len(Journal.objects.all()), 3)
     self.assertEqual(entry.action, "cancel_autorisation")
Пример #24
0
def new_mandat_recap(request):
    connection = Connection.objects.get(pk=request.session["connection"])
    aidant = request.user
    usager = connection.usager
    demarches_description = [
        humanize_demarche_names(demarche) for demarche in connection.demarches
    ]
    duree = connection.get_duree_keyword_display()
    is_remote = connection.mandat_is_remote

    if request.method == "GET":
        form = RecapMandatForm(aidant)
        return render(
            request,
            "aidants_connect_web/new_mandat/new_mandat_recap.html",
            {
                "aidant": aidant,
                "usager": usager,
                "demarches": demarches_description,
                "duree": duree,
                "is_remote": is_remote,
                "form": form,
            },
        )

    else:
        form = RecapMandatForm(aidant=aidant, data=request.POST)

        if form.is_valid():
            now = timezone.now()
            expiration_date = {
                "SHORT": now + timedelta(days=1),
                "LONG": now + timedelta(days=365),
                "EUS_03_20": settings.ETAT_URGENCE_2020_LAST_DAY,
            }
            mandat_expiration_date = expiration_date.get(
                connection.duree_keyword)
            days_before_expiration_date = {
                "SHORT": 1,
                "LONG": 365,
                "EUS_03_20":
                1 + (settings.ETAT_URGENCE_2020_LAST_DAY - now).days,
            }
            mandat_duree = days_before_expiration_date.get(
                connection.duree_keyword)

            try:
                # Add a Journal 'create_attestation' action
                connection.demarches.sort()
                Journal.log_attestation_creation(
                    aidant=aidant,
                    usager=usager,
                    demarches=connection.demarches,
                    duree=mandat_duree,
                    is_remote_mandat=connection.mandat_is_remote,
                    access_token=connection.access_token,
                    attestation_hash=generate_attestation_hash(
                        aidant, usager, connection.demarches,
                        mandat_expiration_date),
                )

                # Create a mandat
                mandat = Mandat.objects.create(
                    organisation=aidant.organisation,
                    usager=usager,
                    duree_keyword=connection.duree_keyword,
                    expiration_date=mandat_expiration_date,
                    is_remote=connection.mandat_is_remote,
                )

                # This loop creates one `autorisation` object per `démarche` in the form
                for demarche in connection.demarches:
                    # Revoke existing demarche autorisation(s)
                    similar_active_autorisations = Autorisation.objects.active(
                    ).filter(
                        mandat__organisation=aidant.organisation,
                        mandat__usager=usager,
                        demarche=demarche,
                    )
                    for similar_active_autorisation in similar_active_autorisations:
                        similar_active_autorisation.revocation_date = now
                        similar_active_autorisation.save(
                            update_fields=["revocation_date"])
                        Journal.log_autorisation_cancel(
                            similar_active_autorisation, aidant)

                    # Create new demarche autorisation
                    autorisation = Autorisation.objects.create(
                        mandat=mandat,
                        demarche=demarche,
                        last_renewal_token=connection.access_token,
                    )
                    Journal.log_autorisation_creation(autorisation, aidant)

            except AttributeError as error:
                log.error("Error happened in Recap")
                log.error(error)
                django_messages.error(
                    request, f"Error with Usager attribute : {error}")
                return redirect("espace_aidant_home")

            except IntegrityError as error:
                log.error("Error happened in Recap")
                log.error(error)
                django_messages.error(request,
                                      f"No Usager was given : {error}")
                return redirect("espace_aidant_home")

            return redirect("new_mandat_success")

        else:
            return render(
                request,
                "aidants_connect_web/new_mandat/new_mandat_recap.html",
                {
                    "aidant": aidant,
                    "usager": usager,
                    "demarche": demarches_description,
                    "duree": duree,
                    "form": form,
                },
            )
Пример #25
0
def confirm_mandat_cancelation(request, mandat_id):
    aidant: Aidant = request.user
    try:
        mandat = Mandat.objects.get(pk=mandat_id,
                                    organisation=aidant.organisation)
    except Mandat.DoesNotExist:
        django_messages.error(request,
                              "Ce mandat est introuvable ou inaccessible.")
        return redirect("espace_aidant_home")

    usager = mandat.usager
    remaining_autorisations = []

    if mandat.is_active:
        for autorisation in mandat.autorisations.filter(revocation_date=None):
            remaining_autorisations.append(
                humanize_demarche_names(autorisation.demarche))

        if request.method == "POST":
            if request.POST:
                autorisation_in_mandat = Autorisation.objects.filter(
                    mandat=mandat)
                for autorisation in autorisation_in_mandat:
                    if not autorisation.revocation_date:
                        autorisation.revocation_date = (
                            autorisation.revocation_date) = timezone.now()
                        autorisation.save(update_fields=["revocation_date"])
                        Journal.log_autorisation_cancel(autorisation, aidant)
                Journal.log_mandat_cancel(mandat, aidant)
                return redirect("mandat_cancelation_success",
                                mandat_id=mandat.id)
            else:
                return render(
                    request,
                    "aidants_connect_web/mandat_auths_cancellation/"
                    "confirm_mandat_cancellation.html",
                    {
                        "aidant":
                        aidant,
                        "usager_name":
                        usager.get_full_name(),
                        "usager_id":
                        usager.id,
                        "mandat":
                        mandat,
                        "remaining_autorisations":
                        remaining_autorisations,
                        "error":
                        "Une erreur s'est produite lors "
                        "de la révocation du mandat",
                    },
                )

    return render(
        request,
        "aidants_connect_web/mandat_auths_cancellation/"
        "confirm_mandat_cancellation.html",
        {
            "aidant": aidant,
            "usager_name": usager.get_full_name(),
            "usager_id": usager.id,
            "mandat": mandat,
            "remaining_autorisations": remaining_autorisations,
        },
    )
Пример #26
0
    def __associate_to_aidant_post(self, request, object_id):
        def redirect_to_list():
            return HttpResponseRedirect(
                reverse("otpadmin:aidants_connect_web_cartetotp_changelist"))

        def redirect_to_object(object_id):
            return HttpResponseRedirect(
                reverse(
                    "otpadmin:aidants_connect_web_cartetotp_change",
                    kwargs={"object_id": object_id},
                ))

        def redirect_to_try_again(object_id):
            return HttpResponseRedirect(
                reverse(
                    "otpadmin:aidants_connect_web_carte_totp_associate",
                    kwargs={"object_id": object_id},
                ))

        if request.POST["aidant"].isnumeric():
            target_aidant_id = int(request.POST["aidant"])
        else:
            self.message_user(request,
                              "L'identifiant de l'aidant est obligatoire.",
                              messages.ERROR)
            return redirect_to_try_again(object_id)
        carte = CarteTOTP.objects.get(id=object_id)

        try:
            # Check if we are trying to associate the card with another aidant: BAD
            if carte.aidant is not None:
                if target_aidant_id != carte.aidant.id:
                    self.message_user(
                        request,
                        f"La carte {carte} est déjà associée à un autre aidant.",
                        messages.ERROR,
                    )
                    return redirect_to_list()

            # link card with aidant
            target_aidant = Aidant.objects.get(id=target_aidant_id)
            if target_aidant.has_a_carte_totp and carte.aidant != target_aidant:
                self.message_user(
                    request,
                    f"L’aidant {target_aidant} a déjà une carte TOTP. "
                    "Vous ne pouvez pas le lier à celle-ci en plus.",
                    messages.ERROR,
                )
                return redirect_to_try_again(object_id)
            carte.aidant = target_aidant
            carte.save()

            # check if totp devices need to be created
            totp_devices = TOTPDevice.objects.filter(user=target_aidant,
                                                     key=carte.seed)
            if totp_devices.count() > 0:
                self.message_user(
                    request,
                    "Tout s'est bien passé. Le TOTP Device existait déjà.")
                return redirect_to_object(object_id)
            else:
                # No Device exists: crate the TOTP Device and save everything
                new_device = carte.createTOTPDevice(confirmed=True)
                new_device.save()
                Journal.log_card_association(request.user, target_aidant,
                                             carte.serial_number)
                self.message_user(
                    request,
                    f"Tout s'est bien passé. La carte {carte} a été associée à "
                    f"{target_aidant} et un TOTP Device a été créé.",
                )
                return redirect_to_list()

        except Aidant.DoesNotExist:
            self.message_user(
                request,
                f"Aucun aidant n’existe avec l'ID {target_aidant_id}. "
                "Veuillez corriger votre saisie.",
                messages.ERROR,
            )
            return redirect_to_try_again(object_id)
        except Exception as e:
            logger.exception(
                "An error occured while trying to associate an aidant"
                "with a new TOTP.")
            self.message_user(
                request,
                f"Quelque chose s’est mal passé durant l'opération. {e}",
                messages.ERROR,
            )

        return HttpResponseRedirect(
            reverse("otpadmin:aidants_connect_web_cartetotp_changelist"))
Пример #27
0
def new_mandat_recap(request):
    connection_id = request.session.get("connection")
    if not connection_id:
        log.error("No connection id found in session")
        return redirect("espace_aidant_home")

    connection = Connection.objects.get(pk=connection_id)

    aidant = request.user
    usager = connection.usager
    demarches_description = [
        humanize_demarche_names(demarche) for demarche in connection.demarches
    ]
    duree = connection.get_duree_keyword_display()
    is_remote = connection.mandat_is_remote

    if request.method == "GET":
        form = RecapMandatForm(aidant)
        return render(
            request,
            "aidants_connect_web/new_mandat/new_mandat_recap.html",
            {
                "aidant": aidant,
                "usager": usager,
                "demarches": demarches_description,
                "duree": duree,
                "is_remote": is_remote,
                "form": form,
            },
        )

    else:
        form = RecapMandatForm(aidant=aidant, data=request.POST)

        if form.is_valid():
            now = timezone.now()
            durationkw = connection.duree_keyword
            mandat_expiration_date = AuthorizationDurations.expiration(
                durationkw, now)
            mandat_duree = AuthorizationDurations.duration(durationkw, now)

            try:
                connection.demarches.sort()

                # Create a mandat
                mandat = Mandat.objects.create(
                    organisation=aidant.organisation,
                    usager=usager,
                    duree_keyword=connection.duree_keyword,
                    expiration_date=mandat_expiration_date,
                    is_remote=connection.mandat_is_remote,
                )

                # Add a Journal 'create_attestation' action
                Journal.log_attestation_creation(
                    aidant=aidant,
                    usager=usager,
                    demarches=connection.demarches,
                    duree=mandat_duree,
                    is_remote_mandat=connection.mandat_is_remote,
                    access_token=connection.access_token,
                    attestation_hash=generate_attestation_hash(
                        aidant, usager, connection.demarches,
                        mandat_expiration_date),
                    mandat=mandat,
                )

                # This loop creates one `autorisation` object per `démarche` in the form
                for demarche in connection.demarches:
                    # Revoke existing demarche autorisation(s)
                    similar_active_autorisations = Autorisation.objects.active(
                    ).filter(
                        mandat__organisation=aidant.organisation,
                        mandat__usager=usager,
                        demarche=demarche,
                    )
                    for similar_active_autorisation in similar_active_autorisations:
                        similar_active_autorisation.revoke(aidant=aidant,
                                                           revocation_date=now)

                    # Create new demarche autorisation
                    autorisation = Autorisation.objects.create(
                        mandat=mandat,
                        demarche=demarche,
                        last_renewal_token=connection.access_token,
                    )
                    Journal.log_autorisation_creation(autorisation, aidant)

            except AttributeError as error:
                log.error("Error happened in Recap")
                log.error(error)
                django_messages.error(
                    request, f"Error with Usager attribute : {error}")
                return redirect("espace_aidant_home")

            except IntegrityError as error:
                log.error("Error happened in Recap")
                log.error(error)
                django_messages.error(request,
                                      f"No Usager was given : {error}")
                return redirect("espace_aidant_home")

            return redirect("new_mandat_success")

        else:
            return render(
                request,
                "aidants_connect_web/new_mandat/new_mandat_recap.html",
                {
                    "aidant": aidant,
                    "usager": usager,
                    "demarche": demarches_description,
                    "duree": duree,
                    "form": form,
                },
            )
Пример #28
0
def get_user_info(connection: Connection) -> tuple:
    fc_base = settings.FC_AS_FS_BASE_URL
    fc_user_info = python_request.get(
        f"{fc_base}/userinfo?schema=openid",
        headers={"Authorization": f"Bearer {connection.access_token}"},
    )
    user_info = fc_user_info.json()

    user_phone = connection.user_phone if len(
        connection.user_phone) > 0 else None

    if user_info.get("birthplace") == "":
        user_info["birthplace"] = None

    user_sub = user_info.get("sub")
    if not user_sub:
        return None, "Unable to find sub in FC user info"

    usager_sub = generate_sha256_hash(
        f"{user_sub}{settings.FC_AS_FI_HASH_SALT}".encode())

    try:
        usager = Usager.objects.get(sub=usager_sub)

        if usager.email != user_info.get("email"):
            usager.email = user_info.get("email")
            usager.save()
            Journal.log_update_email_usager(aidant=connection.aidant,
                                            usager=usager)

        if user_phone is not None and usager.phone != user_phone:
            usager.phone = user_phone
            Journal.log_update_phone_usager(aidant=connection.aidant,
                                            usager=usager)
            usager.save()

        if not usager.preferred_username and user_info.get(
                "preferred_username"):
            usager.preferred_username = user_info.get("preferred_username")
            usager.save()

        return usager, None

    except Usager.DoesNotExist:
        kwargs = {
            "given_name": user_info.get("given_name"),
            "family_name": user_info.get("family_name"),
            "birthdate": user_info.get("birthdate"),
            "gender": user_info.get("gender"),
            "birthplace": user_info.get("birthplace"),
            "birthcountry": user_info.get("birthcountry"),
            "preferred_username": user_info.get("preferred_username"),
            "sub": usager_sub,
            "email": user_info.get("email"),
        }

        if user_phone is not None:
            kwargs["phone"] = user_phone

        try:
            usager = Usager.objects.create(**kwargs)
            return usager, None

        except IntegrityError as e:
            log.error("Error happened in Recap")
            log.error(e)
            return None, f"The FranceConnect ID is not complete: {e}"
Пример #29
0
def fc_callback(request):
    def fc_error(log_msg):
        log.error(log_msg)
        django_messages.error(
            request,
            "Nous avons rencontré une erreur en tentant d'interagir avec "
            "France Connect. C'est probabablement temporaire. Pouvez-vous réessayer "
            "votre requête ?",
        )
        return redirect("new_mandat")

    fc_base = settings.FC_AS_FS_BASE_URL
    fc_callback_uri = f"{settings.FC_AS_FS_CALLBACK_URL}/callback"
    fc_callback_uri_logout = f"{settings.FC_AS_FS_CALLBACK_URL}/logout-callback"
    fc_id = settings.FC_AS_FS_ID
    fc_secret = settings.FC_AS_FS_SECRET
    state = request.GET.get("state")

    try:
        connection = Connection.objects.get(state=state)
    except Connection.DoesNotExist:
        return fc_error(
            f"FC as FS - This state does not seem to exist: {state}")

    if request.GET.get("error"):
        return fc_error(f"FranceConnect returned an error: "
                        f"{request.GET.get('error_description')}")

    if connection.is_expired:
        return fc_error("408: FC connection has expired.")

    code = request.GET.get("code")
    if not code:
        return fc_error("FC AS FS: no code has been provided")

    token_url = f"{fc_base}/token"
    payload = {
        "grant_type": "authorization_code",
        "redirect_uri": fc_callback_uri,
        "client_id": fc_id,
        "client_secret": fc_secret,
        "code": code,
    }
    headers = {"Accept": "application/json"}

    request_for_token = python_request.post(token_url,
                                            data=payload,
                                            headers=headers)

    try:
        content = request_for_token.json()
    except ValueError:  # not a valid JSON
        return fc_error(
            f"Request to {token_url} failed. Status code: "
            f"{request_for_token.status_code}, body: {request_for_token.text}")

    connection.access_token = content.get("access_token")
    if connection.access_token is None:
        return fc_error(
            f"No access_token return when requesting {token_url}. JSON response: "
            f"{repr(content)}")

    connection.save()
    fc_id_token = content.get("id_token")

    try:
        decoded_token = jwt.decode(
            fc_id_token,
            settings.FC_AS_FS_SECRET,
            audience=settings.FC_AS_FS_ID,
            algorithms=["HS256"],
        )
    except ExpiredSignatureError:
        return fc_error("403: token signature has expired.")

    if connection.nonce != decoded_token.get("nonce"):
        return fc_error(
            "FC as FS: The nonce is different than the one expected")

    if connection.is_expired:
        log.info("408: FC connection has expired.")
        return render(request, "408.html", status=408)

    usager, error = get_user_info(connection)
    if error:
        return fc_error(error)

    connection.usager = usager
    connection.save()

    Journal.log_franceconnection_usager(
        aidant=connection.aidant,
        usager=connection.usager,
    )

    logout_base = f"{fc_base}/logout"
    logout_id_token = f"id_token_hint={fc_id_token}"
    logout_state = f"state={state}"
    logout_redirect = f"post_logout_redirect_uri={fc_callback_uri_logout}"
    logout_url = f"{logout_base}?{logout_id_token}&{logout_state}&{logout_redirect}"
    return redirect(logout_url)