From 73afa03d63d81d7b936709f2bb6e30aeaab22410 Mon Sep 17 00:00:00 2001 From: WJH <40563566+loveclever@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:49:23 +0800 Subject: [PATCH] Improve adfs error msg (#5898) * improve error msg * send error msg to sys/org admin * fix code --- frontend/src/components/common/notice-item.js | 8 + seahub/adfs_auth/backends.py | 17 +- seahub/adfs_auth/signals.py | 4 + seahub/adfs_auth/utils.py | 12 +- seahub/adfs_auth/views.py | 214 +++++++++++++++--- seahub/auth/views.py | 6 +- .../management/commands/send_notices.py | 8 + seahub/notifications/models.py | 25 ++ .../templates/notifications/notice_email.html | 3 + seahub/notifications/utils.py | 7 + seahub/organizations/api/admin/saml_config.py | 2 +- 11 files changed, 251 insertions(+), 55 deletions(-) create mode 100644 seahub/adfs_auth/signals.py diff --git a/frontend/src/components/common/notice-item.js b/frontend/src/components/common/notice-item.js index ab349db123..f4f2fb44ef 100644 --- a/frontend/src/components/common/notice-item.js +++ b/frontend/src/components/common/notice-item.js @@ -19,6 +19,7 @@ const MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer'; // const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'; const MSG_TYPE_REPO_MONITOR = 'repo_monitor'; const MSG_TYPE_DELETED_FILES = 'deleted_files'; +const MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed'; class NoticeItem extends React.Component { @@ -282,6 +283,13 @@ class NoticeItem extends React.Component { return { avatar_url : null, notice }; } + if (noticeType === MSG_TYPE_SAML_SSO_FAILED) { + const { error_msg } = detail; + let notice = gettext(error_msg); + + return { avatar_url : null, notice }; + } + // if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) { // } diff --git a/seahub/adfs_auth/backends.py b/seahub/adfs_auth/backends.py index adc5a4407c..dcab39eede 100644 --- a/seahub/adfs_auth/backends.py +++ b/seahub/adfs_auth/backends.py @@ -49,14 +49,6 @@ class Saml2Backend(ModelBackend): logger.error('Session info or attribute mapping are None') return None - if 'ava' not in session_info: - logger.error('"ava" key not found in session_info') - return None - - attributes = session_info['ava'] - if not attributes: - logger.warning('The attributes dictionary is empty') - name_id = session_info.get('name_id', '') if not name_id: logger.error('The name_id is not available. Could not determine user identifier.') @@ -97,6 +89,15 @@ class Saml2Backend(ModelBackend): notify_admins_on_register_complete(user.username) if user: + if 'ava' not in session_info: + logger.warning('"ava" key not found in session_info') + return user + + attributes = session_info['ava'] + if not attributes: + logger.warning('The attributes dictionary is empty') + return user + self.make_profile(user, attributes, attribute_mapping) self.sync_saml_groups(user, attributes) diff --git a/seahub/adfs_auth/signals.py b/seahub/adfs_auth/signals.py new file mode 100644 index 0000000000..c98fad83f1 --- /dev/null +++ b/seahub/adfs_auth/signals.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from django.dispatch import Signal + +saml_sso_failed = Signal() diff --git a/seahub/adfs_auth/utils.py b/seahub/adfs_auth/utils.py index f2c957bbd8..1b66e4a281 100644 --- a/seahub/adfs_auth/utils.py +++ b/seahub/adfs_auth/utils.py @@ -35,18 +35,18 @@ def settings_check(func): error = True else: if not XMLSEC_BINARY_PATH or not CERTS_DIR or not SAML_ATTRIBUTE_MAPPING or not SAML_PROVIDER_IDENTIFIER: - logger.error('ADFS login relevant settings invalid.') + logger.error('ADFS/SAML login relevant settings invalid.') logger.error('SAML_XMLSEC_BINARY_PATH: %s' % XMLSEC_BINARY_PATH) logger.error('SAML_CERTS_DIR: %s' % CERTS_DIR) logger.error('SAML_ATTRIBUTE_MAPPING: %s' % SAML_ATTRIBUTE_MAPPING) logger.error('SAML_PROVIDER_IDENTIFIER: %s' % SAML_PROVIDER_IDENTIFIER) error = True if ENABLE_ADFS_LOGIN and not REMOTE_METADATA_URL: - logger.error('SAML relevant settings invalid.') + logger.error('ADFS/SAML login relevant settings invalid.') logger.error('SAML_REMOTE_METADATA_URL: %s' % REMOTE_METADATA_URL) error = True if error: - raise Exception(_('Error, please contact administrator.')) + raise Exception(_('ADFS/SAML login relevant settings invalid.')) return func(request) return _decorated @@ -66,7 +66,7 @@ def config_settings_loader(request): org_saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id) if not org_saml_config: - raise Exception('Failed to get org %s saml_config' % org_id) + raise Exception('Cannot find an ADFS/SAML config for the organization related to org_id %s.' % org_id) # get org remote_metadata_url remote_metadata_url = org_saml_config.metadata_url @@ -131,6 +131,6 @@ def config_settings_loader(request): conf = SPConfig() conf.load(copy.deepcopy(saml_config)) except Exception as e: - logger.exception('Failed to load saml config, error: %s' % e) - raise Exception('Failed to load saml config, error: %s' % e) + logger.exception('Failed to load adfs/saml config, error: %s' % e) + raise RuntimeError('Failed to load adfs/saml config, error: %s' % e) return conf diff --git a/seahub/adfs_auth/views.py b/seahub/adfs_auth/views.py index c2d0802fe9..1edbc25ee2 100644 --- a/seahub/adfs_auth/views.py +++ b/seahub/adfs_auth/views.py @@ -20,6 +20,7 @@ from django.urls import reverse from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \ HttpResponsePermanentRedirect from django.utils.http import url_has_allowed_host_and_scheme +from django.utils.translation import gettext as _ from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from saml2 import BINDING_HTTP_POST @@ -28,7 +29,6 @@ from saml2.client import Saml2Client from saml2.metadata import entity_descriptor from djangosaml2.cache import IdentityCache, OutstandingQueriesCache from djangosaml2.conf import get_config -from djangosaml2.signals import post_authenticated from seaserv import ccnet_api, seafile_api @@ -42,6 +42,7 @@ from seahub.profile.models import Profile, DetailedProfile from seahub.utils.licenseparse import user_number_over_limit from seahub.utils.file_size import get_quota_from_string from seahub.role_permissions.utils import get_enabled_role_permissions_by_role +from seahub.adfs_auth.signals import saml_sso_failed # Added by khorkin from seahub.base.sudo_mode import update_sudo_mode_ts try: @@ -60,6 +61,15 @@ def _set_subject_id(session, subject_id): session['_saml2_subject_id'] = code(subject_id) +def get_org_admins(org): + org_admins = list() + org_users = ccnet_api.get_org_emailusers(org.url_prefix, -1, -1) + for user in org_users: + if ccnet_api.is_org_staff(org.org_id, user.email): + org_admins.append(user) + return org_admins + + def update_user_profile(user, attribute_mapping, attributes): parse_result = {} for saml_attr, django_attrs in list(attribute_mapping.items()): @@ -111,12 +121,13 @@ def update_user_profile(user, attribute_mapping, attributes): def login(request, org_id=None): + org = None if org_id and int(org_id) > 0: org_id = int(org_id) org = ccnet_api.get_org_by_id(org_id) if not org: logger.error('Cannot find an organization related to org_id %s.' % org_id) - return HttpResponseBadRequest('Cannot find an organization related to org_id %s.' % org_id) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) next_url = settings.LOGIN_REDIRECT_URL if 'next' in request.GET: @@ -129,9 +140,23 @@ def login(request, org_id=None): try: sp_config = get_config(None, request) + except RuntimeError as e: + logger.error(e) + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: ADFS/SAML service error. ' + 'Please report to your organization (company) administrator.')) except Exception as e: logger.error(e) - return HttpResponseBadRequest('Failed to get saml config, please check your ADFS/SAML service.') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) saml_client = Saml2Client(sp_config) session_id, info = saml_client.prepare_for_authenticate(relay_state=next_url) @@ -158,24 +183,50 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre settings.py. The `djangosaml2.backends.Saml2Backend` can be used for this purpose, though some implementations may instead register their own subclasses of Saml2Backend. """ - if 'SAMLResponse' not in request.POST: - return HttpResponseBadRequest('Missing "SAMLResponse" parameter in POST data.') - org = None if org_id and int(org_id) > 0: org_id = int(org_id) org = ccnet_api.get_org_by_id(org_id) if not org: logger.error('Cannot find an organization related to org_id %s.' % org_id) - return HttpResponseBadRequest('Cannot find an organization related to org_id %s.' % org_id) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) else: org_id = -1 + if 'SAMLResponse' not in request.POST: + logger.error('Missing "SAMLResponse" parameter in POST data.') + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: Bad response from ADFS/SAML service. ' + 'Please report to your organization (company) administrator.')) + try: conf = get_config(None, request) + except RuntimeError as e: + logger.error(e) + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: ADFS/SAML service error. ' + 'Please report to your organization (company) administrator.')) except Exception as e: logger.error(e) - return HttpResponseBadRequest('Failed to get saml config, please check your ADFS/SAML service.') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) identity_cache = IdentityCache(request.saml_session) client = Saml2Client(conf, identity_cache=identity_cache) @@ -187,12 +238,33 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre try: response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries) except Exception as e: - logger.error(e) - return HttpResponseBadRequest('SAMLResponse Error') - + logger.error('SAMLResponse Error: %s' % e) + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: Bad response from ADFS/SAML service. ' + 'Please report to your organization (company) administrator.')) if response is None: - logger.error('SAML response is None') - return HttpResponseBadRequest('SAML response has errors. Please check the logs') + logger.error('Invalid SAML Assertion received.') + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: Bad response from ADFS/SAML service. ' + 'Please report to your organization (company) administrator.')) session_id = response.session_id() oq_cache.delete(session_id) @@ -204,18 +276,19 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre is_saml2_connect = parse_qs(urlparse(unquote(relay_state)).query).get('is_saml2_connect', [''])[0] if is_saml2_connect == 'true': if not request.user.is_authenticated: - return HttpResponseBadRequest('Failed to bind SAML, please login first.') + return HttpResponseBadRequest(_('Failed to bind SAML, please login first.')) # get uid and other attrs from session_info name_id = session_info.get('name_id', '') if not name_id: logger.error('The name_id is not available. Could not determine user identifier.') - return HttpResponseBadRequest('Failed to bind SAML, please contact admin.') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) name_id = name_id.text saml_user = SocialAuthUser.objects.get_by_provider_and_uid(SAML_PROVIDER_IDENTIFIER, name_id) if saml_user: - return HttpResponseBadRequest('The SAML user has already been bound to another account.') + logger.error('The SAML user has already been bound to another account.') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) # bind saml user username = request.user.username @@ -236,7 +309,13 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre # check user number limit by license if user_number_over_limit(): - return HttpResponseForbidden('The number of users exceeds the license limit.') + logger.error('The number of users exceeds the license limit.') + # send error msg to admin + error_msg = 'The number of users exceeds the license limit.' + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseForbidden(_('Internal server error. Please contact system administrator.')) # check user number limit by org member quota if org: @@ -245,7 +324,15 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre from seahub.organizations.models import OrgMemberQuota org_members_quota = OrgMemberQuota.objects.get_quota(org_id) if org_members_quota is not None and org_members >= org_members_quota: - return HttpResponseForbidden('The number of users exceeds the organization quota.') + logger.error('The number of users exceeds the organization quota.') + # send error msg to admin + error_msg = 'Failed to create new user: the number of users exceeds the organization quota.' + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + return HttpResponseForbidden(_('Failed to create new user: ' + 'the number of users exceeds the organization quota. ' + 'Please report to your organization (company) administrator.')) # authenticate the remote user logger.debug('Trying to authenticate the user') @@ -254,17 +341,37 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre create_unknown_user=create_unknown_user, org_id=org_id) if user is None: - logger.error('The user is None') - return HttpResponseForbidden("Permission denied") + logger.error('ADFS/SAML single sign-on failed: failed to create user.') + # send error msg to admin + error_msg = 'ADFS/SAML single sign-on failed: failed to create user.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseForbidden(_('Login failed: failed to create user. ' + 'Please report to your organization (company) administrator.')) if not user.is_active: - logger.error('The user is inactive') - return HttpResponseForbidden("Permission denied") + logger.error('ADFS/SAML single sign-on failed: user %s is deactivated.' % user.username) + # send error msg to admin + error_msg = 'ADFS/SAML single sign-on failed: user % is deactivated.' % user.username + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseForbidden(_('Login failed: user is deactivated. ' + 'Please report to your organization (company) administrator.')) auth_login(request, user) _set_subject_id(request.saml_session, session_info['name_id']) - logger.debug('Sending the post_authenticated signal') - post_authenticated.send_robust(sender=user, session_info=session_info) # redirect the user to the view where he came from default_relay_state = settings.LOGIN_REDIRECT_URL @@ -277,18 +384,34 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre def metadata(request, org_id=None): + org = None if org_id and int(org_id) > 0: org_id = int(org_id) org = ccnet_api.get_org_by_id(org_id) if not org: logger.error('Cannot find an organization related to org_id %s.' % org_id) - return HttpResponseBadRequest('Cannot find an organization related to org_id %s.' % org_id) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) try: sp_config = get_config(None, request) + except RuntimeError as e: + logger.error(e) + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: ADFS/SAML service error. ' + 'Please report to your organization (company) administrator.')) except Exception as e: logger.error(e) - return HttpResponseBadRequest('Failed to get saml config, please check your ADFS/SAML service.') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) + sp_metadata = entity_descriptor(sp_config) return HttpResponse( content=str(sp_metadata).encode("utf-8"), @@ -298,16 +421,17 @@ def metadata(request, org_id=None): @login_required def saml2_connect(request, org_id=None): + org = None if org_id and int(org_id) > 0: org_id = int(org_id) org = ccnet_api.get_org_by_id(org_id) if not org: logger.error('Cannot find an organization related to org_id %s.' % org_id) - return HttpResponseBadRequest('Cannot find an organization related to org_id %s.' % org_id) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) if request.user.org.org_id != org_id: - logger.error('User %s does not belong to this organization: %s.' % (request.user.username, org.org_id)) - return HttpResponseBadRequest('Failed to bind SAML, please contact admin.') + logger.error('User %s does not belong to this organization: %s.' % (request.user.username, org.org_name)) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) next_url = settings.LOGIN_REDIRECT_URL if 'next' in request.GET: @@ -321,9 +445,23 @@ def saml2_connect(request, org_id=None): try: sp_config = get_config(None, request) + except RuntimeError as e: + logger.error(e) + # send error msg to admin + error_msg = 'ADFS/SAML service error. Please check and fix the ADFS/SAML service.' + if org: + org_admins = get_org_admins(org) + for org_admin in org_admins: + saml_sso_failed.send(sender=None, to_user=org_admin.email, error_msg=error_msg) + else: + admins = User.objects.get_superusers() + for admin in admins: + saml_sso_failed.send(sender=None, to_user=admin.email, error_msg=error_msg) + return HttpResponseBadRequest(_('Login failed: ADFS/SAML service error. ' + 'Please report to your organization (company) administrator.')) except Exception as e: logger.error(e) - return HttpResponseBadRequest('Failed to get ADFS/SAML config, please check your ADFS/SAML service.') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) saml_client = Saml2Client(sp_config) session_id, info = saml_client.prepare_for_authenticate(relay_state=next_url) @@ -347,18 +485,20 @@ def saml2_disconnect(request, org_id=None): org_id = int(org_id) org = ccnet_api.get_org_by_id(org_id) if not org: - return HttpResponseBadRequest('Cannot find an organization related to org_id %s.' % org_id) + logger.error('Cannot find an organization related to org_id %s.' % org_id) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) if request.user.org.org_id != org_id: - logger.error('User %s does not belong to this organization: %s.' % (request.user.username, org.org_id)) - return HttpResponseBadRequest('Failed to disbind SAML, please contact admin.') + logger.error('User %s does not belong to this organization: %s.' % (request.user.username, org.org_name)) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) username = request.user.username if request.user.enc_password == '!': - return HttpResponseBadRequest('Failed to disbind SAML, please set a password first.') + return HttpResponseBadRequest(_('Failed to unbind SAML, please set a password first.')) + profile = Profile.objects.get_profile_by_user(username) if not profile or not profile.contact_email: - return HttpResponseBadRequest('Failed to disbind SAML, please set a contact email first.') + return HttpResponseBadRequest(_('Failed to unbind SAML, please set a contact email first.')) SocialAuthUser.objects.delete_by_username_and_provider(username, SAML_PROVIDER_IDENTIFIER) next_url = request.GET.get(auth.REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL) @@ -414,11 +554,11 @@ def adfs_compatible_view(request, url_prefix): org = ccnet_api.get_org_by_url_prefix(url_prefix) except Exception as e: logger.error(e) - return HttpResponseBadRequest('login failed, please contact admin') + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) if not org: logger.error('Cannot find an organization related to url_prefix %s.' % url_prefix) - return HttpResponseBadRequest('Cannot find an organization related to url_prefix %s.' % url_prefix) + return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.')) org_id = str(org.org_id) return HttpResponsePermanentRedirect(request.path.replace(url_prefix, org_id)) diff --git a/seahub/auth/views.py b/seahub/auth/views.py index e470b07d25..36deb7878c 100644 --- a/seahub/auth/views.py +++ b/seahub/auth/views.py @@ -501,16 +501,16 @@ def multi_adfs_sso(request): try: org_saml_config = OrgSAMLConfig.objects.get_config_by_domain(domain) if not org_saml_config: - render_data['error_msg'] = "Cannot find a SAML config for the team related to domain %s." % domain + render_data['error_msg'] = "Cannot find an ADFS/SAML config for the team related to domain %s." % domain return render(request, template_name, render_data) if not org_saml_config.domain_verified: render_data['error_msg'] = \ - "The ownership of domain %s has not been verified. Please ask your team admin to verify it." % domain + "The ownership of domain %s has not been verified. Please ask your team admin to verify it." % domain return render(request, template_name, render_data) org_id = org_saml_config.org_id org = ccnet_api.get_org_by_id(org_id) if not org: - render_data['error_msg'] = "Cannot find a SAML config for the team related to domain %s." % domain + render_data['error_msg'] = "Cannot find an ADFS/SAML config for the team related to domain %s." % domain return render(request, template_name, render_data) except Exception as e: logger.error(e) diff --git a/seahub/notifications/management/commands/send_notices.py b/seahub/notifications/management/commands/send_notices.py index eda003bbe5..6e9ea60f19 100644 --- a/seahub/notifications/management/commands/send_notices.py +++ b/seahub/notifications/management/commands/send_notices.py @@ -258,6 +258,11 @@ class Command(BaseCommand): return notice + def format_saml_sso_error_msg(self, notice): + d = json.loads(notice.detail) + notice.error_msg = d['error_msg'] + return notice + def format_sdoc_msg(self, sdoc_queryset, sdoc_notice): sdoc_obj = sdoc_queryset.filter(uuid=sdoc_notice.doc_uuid).first() if not sdoc_obj: @@ -421,6 +426,9 @@ class Command(BaseCommand): elif notice.is_repo_monitor_msg(): notice = self.format_repo_monitor_msg(notice) + elif notice.is_saml_sso_error_msg(): + notice = self.format_saml_sso_error_msg(notice) + if notice is None: continue diff --git a/seahub/notifications/models.py b/seahub/notifications/models.py index 63ad1a3b41..ca5a3548c9 100644 --- a/seahub/notifications/models.py +++ b/seahub/notifications/models.py @@ -76,6 +76,7 @@ MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted' MSG_TYPE_REPO_TRANSFER = 'repo_transfer' MSG_TYPE_REPO_MINOTOR = 'repo_monitor' MSG_TYPE_DELETED_FILES = 'deleted_files' +MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed' USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_' @@ -133,6 +134,11 @@ def repo_transfer_msg_to_json(org_id, repo_owner, repo_id, repo_name): return json.dumps({'org_id': org_id, 'repo_owner': repo_owner, 'repo_id': repo_id, 'repo_name': repo_name}) + +def saml_sso_error_msg_to_json(error_msg): + return json.dumps({'error_msg': error_msg}) + + def get_cache_key_of_unseen_notifications(username): return normalize_cache_key(username, USER_NOTIFICATION_COUNT_CACHE_PREFIX) @@ -309,6 +315,11 @@ class UserNotificationManager(models.Manager): return self._add_user_notification( to_user, MSG_TYPE_REPO_TRANSFER, detail) + def add_saml_sso_error_msg(self, to_user, detail): + """Notify ``to_user`` that saml sso occurred an error + """ + return self._add_user_notification(to_user, MSG_TYPE_SAML_SSO_FAILED, detail) + class UserNotification(models.Model): to_user = LowerCaseCharField(db_index=True, max_length=255) @@ -411,6 +422,9 @@ class UserNotification(models.Model): def is_deleted_files_msg(self): return self.msg_type == MSG_TYPE_DELETED_FILES + def is_saml_sso_error_msg(self): + return self.msg_type == MSG_TYPE_SAML_SSO_FAILED + def user_message_detail_to_dict(self): """Parse user message detail, returns dict contains ``message`` and ``msg_from``. @@ -780,6 +794,8 @@ from seahub.share.signals import share_repo_to_user_successful, \ from seahub.invitations.signals import accept_guest_invitation_successful from seahub.drafts.signals import comment_draft_successful, \ request_reviewer_successful +from seahub.adfs_auth.signals import saml_sso_failed + @receiver(upload_file_successful) def add_upload_file_msg_cb(sender, **kwargs): @@ -918,3 +934,12 @@ def repo_transfer_cb(sender, **kwargs): detail = repo_transfer_msg_to_json(org_id, repo_owner, repo_id, repo_name) UserNotification.objects.add_repo_transfer_msg(to_user, detail) + + +@receiver(saml_sso_failed) +def saml_sso_failed_cb(sender, **kwargs): + to_user = kwargs['to_user'] + error_msg = kwargs['error_msg'] + + detail = saml_sso_error_msg_to_json(error_msg) + UserNotification.objects.add_saml_sso_error_msg(to_user, detail) diff --git a/seahub/notifications/templates/notifications/notice_email.html b/seahub/notifications/templates/notifications/notice_email.html index cddb3a32d1..16468b4953 100644 --- a/seahub/notifications/templates/notifications/notice_email.html +++ b/seahub/notifications/templates/notifications/notice_email.html @@ -65,6 +65,9 @@ You've got {{num}} new notices on {{ site_name }}: {% elif notice.is_deleted_files_msg %}
{% blocktrans with repo_url=notice.repo_url repo_name=notice.repo_name %}A large number of files in your library {{ repo_name }} has been deleted recently.{% endblocktrans %}
+ {% elif notice.is_saml_sso_error_msg %} +{% blocktrans %}{{notice.error_msg}}{% endblocktrans %}
+ {% elif notice.is_repo_monitor_msg %}{% if notice.obj_type == 'file' %} diff --git a/seahub/notifications/utils.py b/seahub/notifications/utils.py index 2174dc14f7..ba415d6c30 100644 --- a/seahub/notifications/utils.py +++ b/seahub/notifications/utils.py @@ -268,6 +268,13 @@ def update_notice_detail(request, notices): except Exception as e: logger.error(e) + elif notice.is_saml_sso_error_msg(): + try: + d = json.loads(notice.detail) + notice.detail = d + except Exception as e: + logger.error(e) + return notices diff --git a/seahub/organizations/api/admin/saml_config.py b/seahub/organizations/api/admin/saml_config.py index ceda33455b..581c4a4799 100644 --- a/seahub/organizations/api/admin/saml_config.py +++ b/seahub/organizations/api/admin/saml_config.py @@ -109,7 +109,7 @@ class OrgVerifyDomain(APIView): saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id) if not saml_config: - error_msg = 'Cannot find a SAML/ADFS config for the organization %s.' % org.org_name + error_msg = 'Cannot find an ADFS/SAML config for the team %s.' % org.org_name return api_error(status.HTTP_404_NOT_FOUND, error_msg) if saml_config.domain != domain: