From 8da5c2663307884c65e7d15e6cd1f32a658c48d5 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 26 Sep 2024 14:24:24 +0200 Subject: [PATCH] splitted update_personal_data() into 2 APIs: update_user_name() and update_user_language() --- src/eduid/webapp/personal_data/schemas.py | 32 ++++++++++++ src/eduid/webapp/personal_data/views.py | 63 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/eduid/webapp/personal_data/schemas.py b/src/eduid/webapp/personal_data/schemas.py index 24721c1c5..102cf8efb 100644 --- a/src/eduid/webapp/personal_data/schemas.py +++ b/src/eduid/webapp/personal_data/schemas.py @@ -27,6 +27,25 @@ class PersonalDataSchema(EduidSchema): legal_name = fields.String(required=False) language = fields.String(required=True, attribute="preferredLanguage") +class UserNameRequestSchema(EduidSchema, CSRFRequestMixin): + given_name = fields.String(required=True, validate=[validate_nonempty]) + chosen_given_name = fields.String(required=False) + surname = fields.String(required=True, validate=[validate_nonempty]) + legal_name = fields.String(required=False) + +class UserNameSchema(EduidSchema): + given_name = fields.String(required=True, attribute="givenName") + chosen_given_name = fields.String(required=False) + surname = fields.String(required=True) + legal_name = fields.String(required=False) + language = fields.String(required=True, attribute="preferredLanguage") + + +class UserLanguageRequestSchema(EduidSchema, CSRFRequestMixin): + language = fields.String(required=True, default="en", validate=validate_language) + +class UserLanguageSchema(EduidSchema): + language = fields.String(required=True, attribute="preferredLanguage") class UserPreferencesSchema(EduidSchema): always_use_security_key = fields.Boolean(required=True, default=True) @@ -49,6 +68,19 @@ class PersonalDataResponsePayload(PersonalDataSchema, CSRFResponseMixin): payload = fields.Nested(PersonalDataResponsePayload) +class UserNameResponseSchema(FluxStandardAction): + class UserNameResponsePayload(UserNameSchema, CSRFResponseMixin): + pass + + payload = fields.Nested(UserNameResponsePayload) + + +class UserLanguageResponseSchema(FluxStandardAction): + class UserLanguageResponsePayload(UserLanguageSchema, CSRFResponseMixin): + pass + + payload = fields.Nested(UserLanguageResponsePayload) + class IdentitiesResponseSchema(FluxStandardAction): class IdentitiesResponsePayload(EmailSchema, CSRFResponseMixin): diff --git a/src/eduid/webapp/personal_data/views.py b/src/eduid/webapp/personal_data/views.py index e45d5f961..d39f8b0f0 100644 --- a/src/eduid/webapp/personal_data/views.py +++ b/src/eduid/webapp/personal_data/views.py @@ -14,6 +14,10 @@ IdentitiesResponseSchema, PersonalDataRequestSchema, PersonalDataResponseSchema, + UserLanguageRequestSchema, + UserLanguageResponseSchema, + UserNameRequestSchema, + UserNameResponseSchema, UserPreferencesRequestSchema, UserPreferencesResponseSchema, ) @@ -82,6 +86,65 @@ def update_personal_data( personal_data = personal_data_user.to_dict() return success_response(payload=personal_data, message=PDataMsg.save_success) +@pd_views.route("/user/name", methods=["POST"]) +@UnmarshalWith(UserNameRequestSchema) +@MarshalWith(UserNameResponseSchema) +@require_user +def update_user_name( + user: User, given_name: str, surname: str, chosen_given_name: str | None = None +) -> FluxData: + personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb) + current_app.logger.debug(f"Trying to save user {user}") + + # disallow change of first name, surname if the user is verified + if not user.identities.is_verified: + personal_data_user.given_name = given_name + personal_data_user.surname = surname + + # set chosen given name to either given name or a subset of given name if supplied + # also allow to set chosen given name to None + if ( + chosen_given_name is not None + and is_valid_chosen_given_name(personal_data_user.given_name, chosen_given_name) is False + ): + return error_response(message=PDataMsg.chosen_given_name_invalid) + + # mypy borked? + # error: Incompatible types in assignment (expression has type "str | None", variable has type "str") + personal_data_user.chosen_given_name = chosen_given_name + + try: + save_and_sync_user(personal_data_user) + except UserOutOfSync: + return error_response(message=CommonMsg.out_of_sync) + current_app.stats.count(name="personal_data_saved", value=1) + current_app.logger.info(f"Saved personal data for user {personal_data_user}") + + personal_data = personal_data_user.to_dict() + return success_response(payload=personal_data, message=PDataMsg.save_success) + +@pd_views.route("/use/language", methods=["POST"]) +@UnmarshalWith(UserLanguageRequestSchema) +@MarshalWith(UserLanguageResponseSchema) +@require_user +def update_user_language( + user: User, language: str +) -> FluxData: + personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb) + current_app.logger.debug(f"Trying to save user {user}") + + personal_data_user.language = language + + try: + save_and_sync_user(personal_data_user) + except UserOutOfSync: + return error_response(message=CommonMsg.out_of_sync) + current_app.stats.count(name="personal_data_saved", value=1) + current_app.logger.info(f"Saved personal data for user {personal_data_user}") + + personal_data = personal_data_user.to_dict() + return success_response(payload=personal_data, message=PDataMsg.save_success) + @pd_views.route("/preferences", methods=["GET"]) @MarshalWith(UserPreferencesResponseSchema)