diff --git a/README.md b/README.md index e8285f23b..21150df06 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ for more details. The information below outlines setting up the server for development or your own environment. For general information on deploying Django see https://docs.djangoproject.com/en/1.11/howto/deployment/. -NOTE: Internal software engineers or other interested parties should follow the documentation for running a Dockerized local development enviornment. For more information see https://github.com/CMSgov/bluebutton-web-server/blob/master/docker-compose/readme.md. +NOTE: Internal software engineers or other interested parties should follow the documentation for running a Docker compose based local development enviornment. For more information see https://github.com/CMSgov/bluebutton-web-server/blob/master/docker-compose/readme.md. Setup ----- diff --git a/apps/dot_ext/loggers.py b/apps/dot_ext/loggers.py index 57d1926a8..0667a12df 100644 --- a/apps/dot_ext/loggers.py +++ b/apps/dot_ext/loggers.py @@ -73,6 +73,7 @@ def create_session_auth_flow_trace(request): client_id_param = request.GET.get("client_id", None) auth_pkce_method = request.GET.get("code_challenge_method", None) + auth_language = request.GET.get("lang", None) if client_id_param: try: @@ -86,6 +87,7 @@ def create_session_auth_flow_trace(request): "auth_require_demographic_scopes": str(application.require_demographic_scopes), "auth_client_id": application.client_id, "auth_pkce_method": auth_pkce_method, + "auth_language": auth_language, } set_session_auth_flow_trace(request, auth_flow_dict) @@ -94,7 +96,8 @@ def create_session_auth_flow_trace(request): with transaction.atomic(): AuthFlowUuid.objects.create(auth_uuid=new_auth_uuid, client_id=application.client_id, - auth_pkce_method=auth_pkce_method) + auth_pkce_method=auth_pkce_method, + auth_language=auth_language) except IntegrityError: pass except Application.DoesNotExist: @@ -106,6 +109,7 @@ def create_session_auth_flow_trace(request): "auth_require_demographic_scopes": "", "auth_client_id": "", "auth_pkce_method": "", + "auth_language": "", } set_session_auth_flow_trace(request, auth_flow_dict) @@ -160,6 +164,8 @@ def set_session_values_from_auth_flow_uuid(request, auth_flow_uuid): request.session['auth_crosswalk_action'] = auth_flow_uuid.auth_crosswalk_action if auth_flow_uuid.auth_share_demographic_scopes is not None: request.session['auth_share_demographic_scopes'] = str(auth_flow_uuid.auth_share_demographic_scopes) + if auth_flow_uuid.auth_language is not None: + request.session['auth_language'] = auth_flow_uuid.auth_language try: application = Application.objects.get(client_id=auth_flow_uuid.client_id) diff --git a/apps/dot_ext/migrations/0008_authflowuuid_auth_language_and_more.py b/apps/dot_ext/migrations/0008_authflowuuid_auth_language_and_more.py new file mode 100644 index 000000000..64bcce056 --- /dev/null +++ b/apps/dot_ext/migrations/0008_authflowuuid_auth_language_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.11 on 2024-07-07 22:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dot_ext', '0007_merge_20231020_2004'), + ] + + operations = [ + migrations.AddField( + model_name='authflowuuid', + name='auth_language', + field=models.CharField(max_length=12, null=True), + ), + migrations.AddField( + model_name='authflowuuidcopy', + name='auth_language', + field=models.CharField(max_length=12, null=True), + ), + migrations.AlterField( + model_name='application', + name='data_access_type', + field=models.CharField(choices=[('ONE_TIME', 'ONE_TIME - No refresh token needed.'), ('RESEARCH_STUDY', 'RESEARCH_STUDY - No expiration.'), ('THIRTEEN_MONTH', 'THIRTEEN_MONTH - Access expires in 13-months.')], default='THIRTEEN_MONTH', max_length=16, null=True, verbose_name='Data Access Type:'), + ), + ] diff --git a/apps/dot_ext/models.py b/apps/dot_ext/models.py index e8ffbaa10..7e4cb0442 100644 --- a/apps/dot_ext/models.py +++ b/apps/dot_ext/models.py @@ -431,6 +431,7 @@ class AuthFlowUuid(models.Model): created = models.DateTimeField(auto_now_add=True, null=True) auth_crosswalk_action = models.CharField(max_length=1, null=True) auth_share_demographic_scopes = models.BooleanField(null=True) + auth_language = models.CharField(max_length=12, null=True) def __str__(self): return str(self.auth_uuid) @@ -463,6 +464,7 @@ class AuthFlowUuidCopy(models.Model): created = models.DateTimeField(null=True) auth_crosswalk_action = models.CharField(max_length=1, null=True) auth_share_demographic_scopes = models.BooleanField(null=True) + auth_language = models.CharField(max_length=12, null=True) def __str__(self): return str(self.auth_uuid) diff --git a/apps/fhir/server/loggers.py b/apps/fhir/server/loggers.py index be4597b0b..2fb4f0ba5 100644 --- a/apps/fhir/server/loggers.py +++ b/apps/fhir/server/loggers.py @@ -1,5 +1,6 @@ import apps.logging.request_logger as logging +from apps.logging.utils import lookup_language """ Logger functions for fhir/server module @@ -13,6 +14,8 @@ def log_match_fhir_id(request, fhir_id, mbi_hash, hicn_hash, used in match_fhir_id() ''' match_fhir_id_logger = logging.getLogger(logging.AUDIT_AUTHN_MATCH_FHIR_ID_LOGGER, request) + # splunk dashboard auth flow baseSearch4 + lang = lookup_language(request) match_fhir_id_logger.info({ "type": "fhir.server.authentication.match_fhir_id", "fhir_id": fhir_id, @@ -21,4 +24,5 @@ def log_match_fhir_id(request, fhir_id, mbi_hash, hicn_hash, "match_found": match_found, "hash_lookup_type": hash_lookup_type, "hash_lookup_mesg": hash_lookup_mesg, + "auth_language": lang, }) diff --git a/apps/logging/serializers.py b/apps/logging/serializers.py index 9e16fd89b..0a88b1965 100644 --- a/apps/logging/serializers.py +++ b/apps/logging/serializers.py @@ -1,5 +1,6 @@ import json import hashlib +from apps.logging.utils import lookup_language class DataAccessGrantSerializer: @@ -39,9 +40,10 @@ class Token: tkn = None action = None - def __init__(self, obj, action=None): + def __init__(self, obj, action=None, request=None): self.tkn = obj self.action = action + self.request = request def to_dict(self): # seems like this should be a serializer @@ -56,7 +58,8 @@ def to_dict(self): scopes = " ".join(scopes_dict.keys()) else: scopes = "" - + # splunk dashboard auth flow dashboard baseSearch12 + lang = lookup_language(self.request) result = { "type": "AccessToken", "action": self.action, @@ -84,6 +87,7 @@ def to_dict(self): "fhir_id": getattr(crosswalk, "fhir_id", None), "user_id_type": getattr(crosswalk, "user_id_type", None), }, + "auth_language": lang, } return result diff --git a/apps/logging/signals.py b/apps/logging/signals.py index a0046ef4c..0f8ae2e80 100644 --- a/apps/logging/signals.py +++ b/apps/logging/signals.py @@ -28,12 +28,14 @@ FHIRResponseForAuth, ) +from apps.logging.utils import lookup_language + @receiver(app_authorized) def handle_token_created(sender, request, token, **kwargs): # Get auth flow dict from session for logging token_logger = logging.getLogger(logging.AUDIT_AUTHZ_TOKEN_LOGGER, request) - token_logger.info(Token(token, action="authorized").to_dict()) + token_logger.info(Token(token, action="authorized", request=request).to_dict()) @receiver(beneficiary_authorized_application) @@ -62,7 +64,8 @@ def handle_app_authorized(sender, request, auth_status, auth_status_code, user, # TODO consider logging exception name here # once we get the generic logger hooked up pass - + # splunk dashboard auth flow baseSearch11 + lang = lookup_language(request) log_dict = { "type": "Authorization", "auth_status": auth_status, @@ -83,6 +86,7 @@ def handle_app_authorized(sender, request, auth_status, auth_status_code, user, "access_token_delete_cnt": access_token_delete_cnt, "refresh_token_delete_cnt": access_token_delete_cnt, "data_access_grant_delete_cnt": data_access_grant_delete_cnt, + "auth_language": lang, } token_logger.info(log_dict) diff --git a/apps/logging/utils.py b/apps/logging/utils.py index 3c8e2a8a6..48522aa15 100644 --- a/apps/logging/utils.py +++ b/apps/logging/utils.py @@ -1,12 +1,27 @@ import io import apps.logging.request_logger as logging +from apps.dot_ext.loggers import ( + get_session_auth_flow_trace, +) """ Utility functions for logging, and logging manipulations (used in tests) """ +def lookup_language(request): + # keep lang code from session if presents + # otherwise grab it from parameters + if request is not None: + auth_dict = get_session_auth_flow_trace(request) + qparam_lang = request.GET.get('lang', request.GET.get('Lang', "")) + qparam_lang = qparam_lang if qparam_lang else request.POST.get('lang', request.POST.get('Lang', "")) + return auth_dict.get('auth_language', qparam_lang) + else: + return "" + + def format_timestamp(dt): """ Returns an ISO 6801 format string in UTC that works well with AWS Glue/Athena diff --git a/apps/mymedicare_cb/authorization.py b/apps/mymedicare_cb/authorization.py index 328c4d790..8a4583c58 100644 --- a/apps/mymedicare_cb/authorization.py +++ b/apps/mymedicare_cb/authorization.py @@ -14,6 +14,7 @@ from .signals import response_hook_wrapper from .validators import is_mbi_format_valid, is_mbi_format_synthetic +from apps.logging.utils import lookup_language MSG_SLS_RESP_MISSING_AUTHTOKEN = "Exchange auth_token is missing in response error" @@ -365,7 +366,8 @@ def validate_asserts(self, request, asserts, err_enum): # asserts is a list of tuple : (boolean expression, err message) # iterate boolean expressions and log err message if the expression evalaute to true logger = logging.getLogger(logging.AUDIT_AUTHN_SLS_LOGGER, request) - + # splunk dashboard auth flow dashboard baseSearch3 + lang = lookup_language(request) log_dict = { "type": "Authentication:start", "sls_status": "FAIL", @@ -380,6 +382,7 @@ def validate_asserts(self, request, asserts, err_enum): "sls_mbi_format_synthetic": None, "sls_hicn_hash": None, "sls_mbi_hash": None, + "auth_language": lang, } for t in asserts: @@ -413,7 +416,8 @@ def validate_asserts(self, request, asserts, err_enum): def log_event(self, request, extra): logger = logging.getLogger(logging.AUDIT_AUTHN_SLS_LOGGER, request) - + # splunk dashboard auth flow dashboard baseSearch3 + lang = lookup_language(request) log_dict = { "type": "Authentication:start", "sub": self.user_id, @@ -428,6 +432,7 @@ def log_event(self, request, extra): "sls_mbi_format_synthetic": self.mbi_format_synthetic, "sls_hicn_hash": self.hicn_hash, "sls_mbi_hash": self.mbi_hash, + "auth_language": lang, } log_dict.update(extra) @@ -435,10 +440,13 @@ def log_event(self, request, extra): def log_authn_success(self, request, extra): logger = logging.getLogger(logging.AUDIT_AUTHN_SLS_LOGGER, request) + # splunk dashboard auth flow dashboard baseSearch7 + lang = lookup_language(request) log_dict = { "type": "Authentication:success", "sub": self.user_id, "user": None, + "auth_language": lang, } log_dict.update(extra) logger.info(log_dict) diff --git a/apps/mymedicare_cb/models.py b/apps/mymedicare_cb/models.py index 17d0cefd8..2527df402 100644 --- a/apps/mymedicare_cb/models.py +++ b/apps/mymedicare_cb/models.py @@ -11,6 +11,7 @@ from apps.fhir.server.authentication import match_fhir_id from .authorization import OAuth2ConfigSLSx, MedicareCallbackExceptionType +from apps.logging.utils import lookup_language class BBMyMedicareCallbackCrosswalkCreateException(APIException): @@ -56,7 +57,8 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request=None): mbi_hash=slsx_client.mbi_hash, hicn_hash=slsx_client.hicn_hash, request=request ) - + # splunk auth flow baseSearch5 baseSearch6a + lang = lookup_language(request) log_dict = { "type": "mymedicare_cb:get_and_update_user", "subject": slsx_client.user_id, @@ -66,6 +68,7 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request=None): "hash_lookup_type": hash_lookup_type, "crosswalk": {}, "crosswalk_before": {}, + "auth_language": lang, } # Init for types of crosswalk updates. @@ -176,7 +179,8 @@ def get_and_update_user(slsx_client: OAuth2ConfigSLSx, request=None): def create_beneficiary_record(slsx_client: OAuth2ConfigSLSx, fhir_id=None, user_id_type="H", request=None): logger = logging.getLogger(logging.AUDIT_AUTHN_MED_CALLBACK_LOGGER, request) - + # splunk dashboard auth flow baseSearch6b + lang = lookup_language(request) log_dict = { "type": "mymedicare_cb:create_beneficiary_record", "username": slsx_client.user_id, @@ -184,6 +188,7 @@ def create_beneficiary_record(slsx_client: OAuth2ConfigSLSx, fhir_id=None, user_ "user_mbi_hash": slsx_client.mbi_hash, "user_hicn_hash": slsx_client.hicn_hash, "crosswalk": {}, + "auth_language": lang, } _validate_asserts(logger, log_dict, [ diff --git a/apps/testclient/templates/authorize.html b/apps/testclient/templates/authorize.html index ffb6beacb..e2662fada 100644 --- a/apps/testclient/templates/authorize.html +++ b/apps/testclient/templates/authorize.html @@ -39,6 +39,9 @@