diff --git a/msystems/apps.py b/msystems/apps.py index f3d3309..ffb9200 100644 --- a/msystems/apps.py +++ b/msystems/apps.py @@ -6,6 +6,13 @@ # URL to be redirected to after successful login "mpass_login_redirect": "", + "mpass_key_first_name": "FirstName", + "mpass_key_last_name": "LastName", + "mpass_key_dob": "BirthDate", + "mpass_key_roles": "Role", + "mpass_key_legal_entities": "OrganizationAdministrator", + # "mpass_key_legal_entities": "AdministeredLegalEntity", + # Mpass configurations "mpass_config": { # Strict mode: SAML responses must be validated strictly. @@ -89,7 +96,13 @@ # The same as mpass private key "service_private_key": "", # Mconnect certificate, PEM string format - "mconnect_certificate": "" + "mconnect_certificate": "", + + # Get Person Soap Header default values + "get_person_calling_user": "", # len 13 + "get_person_calling_entity": "", # len 13 + "get_person_call_basis": "", # max len 256 + "get_person_call_reason": "", # max len 512 }, "voucher_config": { # The same as mpass cert @@ -106,6 +119,12 @@ class MsystemsConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "msystems" + mpass_key_first_name = None + mpass_key_last_name = None + mpass_dob = None + mpass_key_roles = None + mpass_key_legal_entities = None + # DO NOT CHANGE THIS #### ADMIN = "Admin" INSPECTOR = "Inspector" diff --git a/msystems/client/mconnect.py b/msystems/client/mconnect.py index cc00ddb..d78a4f0 100644 --- a/msystems/client/mconnect.py +++ b/msystems/client/mconnect.py @@ -1,40 +1,13 @@ -from zeep import Client, Plugin, Settings -from zeep.exceptions import SignatureVerificationFailed +import logging -from msystems.apps import MsystemsConfig -from msystems.xml_utils import add_signature, verify_signature, add_timestamp, verify_timestamp - - -class MconnectClientError(Exception): - pass - - -class SoapWssePlugin(Plugin): - def __init__(self, service_private_key, service_certificate, mconnect_certificate): - self.service_certificate = service_certificate - self.service_private_key = service_private_key - self.mconnect_certificate = mconnect_certificate - - def egress(self, envelope, http_headers, operation, binding_options): - root = envelope - - add_timestamp(root) - add_signature(root, self.service_private_key, self.service_certificate) - - return envelope, http_headers +from zeep import Client, Settings - def ingress(self, envelope, http_headers, operation): - try: - verify_timestamp(envelope) - except ValueError as e: - raise MconnectClientError(str(e)) - - try: - verify_signature(envelope, self.mconnect_certificate) - except SignatureVerificationFailed: - raise MconnectClientError("Envelope signature verification failed") +from core.models import User +from msystems.apps import MsystemsConfig +from msystems.client.utils import SoapWssePlugin, SoapClientError +from policyholder.models import PolicyHolder - return envelope, http_headers +logger = logging.getLogger(__name__) class MconnectClient: @@ -43,13 +16,30 @@ def __init__(self): self.url = MsystemsConfig.mconnect_config['url'] settings = Settings(strict=False, raw_response=True) - self.client = Client(self.url, settings, + self.client = Client(wsdl=self.url, + settings=settings, plugins=[SoapWssePlugin(MsystemsConfig.mconnect_config['service_private_key'], MsystemsConfig.mconnect_config['service_certificate'], MsystemsConfig.mconnect_config['mconnect_certificate'])]) - def get_person(self, idpn): - service_handle = self.client.service.get('GetPerson') + def get_person(self, idnp: str, user: User = None, economic_unit: PolicyHolder = None): + service_handle = self.client.service['GetPerson'] if not service_handle: - raise MconnectClientError("Service GetPerson not found") - return service_handle(IDPN=idpn) + raise SoapClientError("Service GetPerson not found") + + # Bounds for headers and idnp from Mconnect documentation, should not be exceeded in normal operation + # Added for extra protection + + headers = { + "CallingUser": user.username[:13] or MsystemsConfig.mconnect_config['get_person_calling_user'][:13], + "CallingEntity": economic_unit.trade_name[:13] + or MsystemsConfig.mconnect_config['get_person_calling_entity'][:13], + "CallBasis": MsystemsConfig.mconnect_config['get_person_call_basis'][:256], + "CallReason": MsystemsConfig.mconnect_config['get_person_call_reason'][:512] + } + + try: + return service_handle(IDNP=idnp[:13], _soapheaders=headers) + except Exception as e: + logger.error("Error during Mconnect request", exc_info=e) + raise SoapClientError(str(e)) diff --git a/msystems/client/utils.py b/msystems/client/utils.py new file mode 100644 index 0000000..212e8cf --- /dev/null +++ b/msystems/client/utils.py @@ -0,0 +1,36 @@ +from zeep import Plugin +from zeep.exceptions import SignatureVerificationFailed + +from msystems.xml_utils import add_timestamp, add_signature, verify_timestamp, verify_signature + + +class SoapClientError(Exception): + pass + + +class SoapWssePlugin(Plugin): + def __init__(self, service_private_key, service_certificate, mconnect_certificate): + self.service_certificate = service_certificate + self.service_private_key = service_private_key + self.mconnect_certificate = mconnect_certificate + + def egress(self, envelope, http_headers, operation, binding_options): + root = envelope + + add_timestamp(root) + add_signature(root, self.service_private_key, self.service_certificate) + + return envelope, http_headers + + def ingress(self, envelope, http_headers, operation): + try: + verify_timestamp(envelope) + except ValueError as e: + raise SoapClientError(str(e)) + + try: + verify_signature(envelope, self.mconnect_certificate) + except SignatureVerificationFailed: + raise SoapClientError("Envelope signature verification failed") + + return envelope, http_headers diff --git a/msystems/migrations/0007_add_languages.py b/msystems/migrations/0007_add_languages.py new file mode 100644 index 0000000..93628e7 --- /dev/null +++ b/msystems/migrations/0007_add_languages.py @@ -0,0 +1,23 @@ +from django.db import migrations + +language_code_ro = "ro" +language_code_ru = "ru" + + +def on_migration(apps, schema_editor): + language_model = apps.get_model("core", "language") + if not language_model.objects.filter(code=language_code_ro).exists(): + language_model(code=language_code_ro, name="Română").save() + if not language_model.objects.filter(code=language_code_ru).exists(): + language_model(code=language_code_ru, name="Русский").save() + + +class Migration(migrations.Migration): + dependencies = [ + ('msystems', '0006_add_bill_query_rights'), + ('core', '0001_initial'), + ] + + operations = [ + migrations.RunPython(on_migration, migrations.RunPython.noop), + ] diff --git a/msystems/services/saml_user_service.py b/msystems/services/mpass_user_service.py similarity index 92% rename from msystems/services/saml_user_service.py rename to msystems/services/mpass_user_service.py index dad6f15..921686c 100644 --- a/msystems/services/saml_user_service.py +++ b/msystems/services/mpass_user_service.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) -class SamlUserService: +class MpassUserService: location = None def __init__(self): @@ -51,8 +51,8 @@ def _get_or_create_user(self, username: str, user_data: dict): def _create_user(self, username: str, user_data: dict) -> User: i_user = InteractiveUser( login_name=username, - other_names=user_data.get('FirstName')[0], - last_name=user_data.get('LastName')[0], + other_names=user_data.get(MsystemsConfig.mpass_key_first_name)[0], + last_name=user_data.get(MsystemsConfig.mpass_key_last_name)[0], language_id=MsystemsConfig.default_mpass_language, audit_user_id=0, is_associated=False, @@ -70,28 +70,28 @@ def _create_user(self, username: str, user_data: dict) -> User: return core_user def _update_user(self, user: User, user_data: dict) -> None: - data_first_name = user_data.get('FirstName')[0] - data_last_name = user_data.get('LastName')[0] + data_first_name = user_data.get(MsystemsConfig.mpass_key_first_name)[0] + data_last_name = user_data.get(MsystemsConfig.mpass_key_last_name)[0] # Update first and last name if they are different if user.i_user.other_names != data_first_name or user.i_user.last_name != data_last_name: self._update_user_name(user.i_user, data_first_name, data_last_name) def _update_user_legal_entities(self, user: User, user_data: dict) -> None: - legal_entities = self._parse_legal_entities(user_data.get('AdministeredLegalEntity', [])) + legal_entities = self._parse_legal_entities(user_data.get(MsystemsConfig.mpass_key_legal_entities, [])) policyholders = [self._get_or_create_policy_holder(user, line[1], line[0]) for line in legal_entities] self._delete_old_user_policyholders(user, policyholders) self._add_new_user_policyholders(user, policyholders) def _update_user_roles(self, user, user_data): - msystem_roles_list = user_data.get('Role', [MsystemsConfig.EMPLOYER]) + mpass_roles_list = user_data.get(MsystemsConfig.mpass_key_roles, [MsystemsConfig.EMPLOYER]) - for role in msystem_roles_list: + for role in mpass_roles_list: self._validate_incoming_roles(role) - self._delete_old_user_roles(user, msystem_roles_list) - self._add_new_user_roles(user, msystem_roles_list) + self._delete_old_user_roles(user, mpass_roles_list) + self._add_new_user_roles(user, mpass_roles_list) def _update_user_name(self, i_user, first_name, last_name): i_user.save_history() diff --git a/msystems/views/mpass.py b/msystems/views/mpass.py index 46b208b..3a4144c 100644 --- a/msystems/views/mpass.py +++ b/msystems/views/mpass.py @@ -6,7 +6,7 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.clickjacking import xframe_options_exempt from msystems.apps import MsystemsConfig -from msystems.services.saml_user_service import SamlUserService +from msystems.services.mpass_user_service import MpassUserService from onelogin.saml2.auth import OneLogin_Saml2_Auth, OneLogin_Saml2_Settings from graphql_jwt.decorators import jwt_cookie from graphql_jwt.shortcuts import get_token, create_refresh_token @@ -83,7 +83,7 @@ def _handle_acs_login(request): username = auth.get_nameid() user_data = auth.get_attributes() - user = SamlUserService().login(username=username, user_data=user_data) + user = MpassUserService().login(username=username, user_data=user_data) # Tokens to be set in cookies request.jwt_token = get_token(user) diff --git a/msystems/xml_utils.py b/msystems/xml_utils.py index 9ad725c..29fcf4d 100644 --- a/msystems/xml_utils.py +++ b/msystems/xml_utils.py @@ -1,5 +1,6 @@ import re import datetime as py_datetime + from zeep.wsse.signature import _make_sign_key, _sign_envelope_with_key, _make_verify_key, _verify_envelope_with_key from lxml import etree