1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 15:53:28 +00:00

Improve adfs error msg (#5898)

* improve error msg

* send error msg to sys/org admin

* fix code
This commit is contained in:
WJH
2024-01-17 14:49:23 +08:00
committed by GitHub
parent c9ca6efb89
commit 73afa03d63
11 changed files with 251 additions and 55 deletions

View File

@@ -19,6 +19,7 @@ const MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer';
// const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'; // const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted';
const MSG_TYPE_REPO_MONITOR = 'repo_monitor'; const MSG_TYPE_REPO_MONITOR = 'repo_monitor';
const MSG_TYPE_DELETED_FILES = 'deleted_files'; const MSG_TYPE_DELETED_FILES = 'deleted_files';
const MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed';
class NoticeItem extends React.Component { class NoticeItem extends React.Component {
@@ -282,6 +283,13 @@ class NoticeItem extends React.Component {
return { avatar_url : null, notice }; 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) { // if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) {
// } // }

View File

@@ -49,14 +49,6 @@ class Saml2Backend(ModelBackend):
logger.error('Session info or attribute mapping are None') logger.error('Session info or attribute mapping are None')
return 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', '') name_id = session_info.get('name_id', '')
if not name_id: if not name_id:
logger.error('The name_id is not available. Could not determine user identifier.') 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) notify_admins_on_register_complete(user.username)
if user: 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.make_profile(user, attributes, attribute_mapping)
self.sync_saml_groups(user, attributes) self.sync_saml_groups(user, attributes)

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from django.dispatch import Signal
saml_sso_failed = Signal()

View File

@@ -35,18 +35,18 @@ def settings_check(func):
error = True error = True
else: else:
if not XMLSEC_BINARY_PATH or not CERTS_DIR or not SAML_ATTRIBUTE_MAPPING or not SAML_PROVIDER_IDENTIFIER: 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_XMLSEC_BINARY_PATH: %s' % XMLSEC_BINARY_PATH)
logger.error('SAML_CERTS_DIR: %s' % CERTS_DIR) logger.error('SAML_CERTS_DIR: %s' % CERTS_DIR)
logger.error('SAML_ATTRIBUTE_MAPPING: %s' % SAML_ATTRIBUTE_MAPPING) logger.error('SAML_ATTRIBUTE_MAPPING: %s' % SAML_ATTRIBUTE_MAPPING)
logger.error('SAML_PROVIDER_IDENTIFIER: %s' % SAML_PROVIDER_IDENTIFIER) logger.error('SAML_PROVIDER_IDENTIFIER: %s' % SAML_PROVIDER_IDENTIFIER)
error = True error = True
if ENABLE_ADFS_LOGIN and not REMOTE_METADATA_URL: 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) logger.error('SAML_REMOTE_METADATA_URL: %s' % REMOTE_METADATA_URL)
error = True error = True
if error: if error:
raise Exception(_('Error, please contact administrator.')) raise Exception(_('ADFS/SAML login relevant settings invalid.'))
return func(request) return func(request)
return _decorated return _decorated
@@ -66,7 +66,7 @@ def config_settings_loader(request):
org_saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id) org_saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id)
if not org_saml_config: 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 # get org remote_metadata_url
remote_metadata_url = org_saml_config.metadata_url remote_metadata_url = org_saml_config.metadata_url
@@ -131,6 +131,6 @@ def config_settings_loader(request):
conf = SPConfig() conf = SPConfig()
conf.load(copy.deepcopy(saml_config)) conf.load(copy.deepcopy(saml_config))
except Exception as e: except Exception as e:
logger.exception('Failed to load saml config, error: %s' % e) logger.exception('Failed to load adfs/saml config, error: %s' % e)
raise Exception('Failed to load saml config, error: %s' % e) raise RuntimeError('Failed to load adfs/saml config, error: %s' % e)
return conf return conf

View File

@@ -20,6 +20,7 @@ from django.urls import reverse
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \ from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
HttpResponsePermanentRedirect HttpResponsePermanentRedirect
from django.utils.http import url_has_allowed_host_and_scheme 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.http import require_POST
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
@@ -28,7 +29,6 @@ from saml2.client import Saml2Client
from saml2.metadata import entity_descriptor from saml2.metadata import entity_descriptor
from djangosaml2.cache import IdentityCache, OutstandingQueriesCache from djangosaml2.cache import IdentityCache, OutstandingQueriesCache
from djangosaml2.conf import get_config from djangosaml2.conf import get_config
from djangosaml2.signals import post_authenticated
from seaserv import ccnet_api, seafile_api 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.licenseparse import user_number_over_limit
from seahub.utils.file_size import get_quota_from_string from seahub.utils.file_size import get_quota_from_string
from seahub.role_permissions.utils import get_enabled_role_permissions_by_role from seahub.role_permissions.utils import get_enabled_role_permissions_by_role
from seahub.adfs_auth.signals import saml_sso_failed
# Added by khorkin # Added by khorkin
from seahub.base.sudo_mode import update_sudo_mode_ts from seahub.base.sudo_mode import update_sudo_mode_ts
try: try:
@@ -60,6 +61,15 @@ def _set_subject_id(session, subject_id):
session['_saml2_subject_id'] = code(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): def update_user_profile(user, attribute_mapping, attributes):
parse_result = {} parse_result = {}
for saml_attr, django_attrs in list(attribute_mapping.items()): 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): def login(request, org_id=None):
org = None
if org_id and int(org_id) > 0: if org_id and int(org_id) > 0:
org_id = int(org_id) org_id = int(org_id)
org = ccnet_api.get_org_by_id(org_id) org = ccnet_api.get_org_by_id(org_id)
if not org: if not org:
logger.error('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('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 next_url = settings.LOGIN_REDIRECT_URL
if 'next' in request.GET: if 'next' in request.GET:
@@ -129,9 +140,23 @@ def login(request, org_id=None):
try: try:
sp_config = get_config(None, request) 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: except Exception as e:
logger.error(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) saml_client = Saml2Client(sp_config)
session_id, info = saml_client.prepare_for_authenticate(relay_state=next_url) 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, settings.py. The `djangosaml2.backends.Saml2Backend` can be used for this purpose,
though some implementations may instead register their own subclasses of Saml2Backend. 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 org = None
if org_id and int(org_id) > 0: if org_id and int(org_id) > 0:
org_id = int(org_id) org_id = int(org_id)
org = ccnet_api.get_org_by_id(org_id) org = ccnet_api.get_org_by_id(org_id)
if not org: if not org:
logger.error('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('Cannot find an organization related to org_id %s.' % org_id) return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.'))
else: else:
org_id = -1 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: try:
conf = get_config(None, request) 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: except Exception as e:
logger.error(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) identity_cache = IdentityCache(request.saml_session)
client = Saml2Client(conf, identity_cache=identity_cache) client = Saml2Client(conf, identity_cache=identity_cache)
@@ -187,12 +238,33 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre
try: try:
response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries) response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries)
except Exception as e: except Exception as e:
logger.error(e) logger.error('SAMLResponse Error: %s' % e)
return HttpResponseBadRequest('SAMLResponse Error') # 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: if response is None:
logger.error('SAML response is None') logger.error('Invalid SAML Assertion received.')
return HttpResponseBadRequest('SAML response has errors. Please check the logs') # 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() session_id = response.session_id()
oq_cache.delete(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] is_saml2_connect = parse_qs(urlparse(unquote(relay_state)).query).get('is_saml2_connect', [''])[0]
if is_saml2_connect == 'true': if is_saml2_connect == 'true':
if not request.user.is_authenticated: 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 # get uid and other attrs from session_info
name_id = session_info.get('name_id', '') name_id = session_info.get('name_id', '')
if not name_id: if not name_id:
logger.error('The name_id is not available. Could not determine user identifier.') 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 name_id = name_id.text
saml_user = SocialAuthUser.objects.get_by_provider_and_uid(SAML_PROVIDER_IDENTIFIER, name_id) saml_user = SocialAuthUser.objects.get_by_provider_and_uid(SAML_PROVIDER_IDENTIFIER, name_id)
if saml_user: 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 # bind saml user
username = request.user.username 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 # check user number limit by license
if user_number_over_limit(): 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 # check user number limit by org member quota
if org: if org:
@@ -245,7 +324,15 @@ def assertion_consumer_service(request, org_id=None, attribute_mapping=None, cre
from seahub.organizations.models import OrgMemberQuota from seahub.organizations.models import OrgMemberQuota
org_members_quota = OrgMemberQuota.objects.get_quota(org_id) org_members_quota = OrgMemberQuota.objects.get_quota(org_id)
if org_members_quota is not None and org_members >= org_members_quota: 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 # authenticate the remote user
logger.debug('Trying to authenticate the 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, create_unknown_user=create_unknown_user,
org_id=org_id) org_id=org_id)
if user is None: if user is None:
logger.error('The user is None') logger.error('ADFS/SAML single sign-on failed: failed to create user.')
return HttpResponseForbidden("Permission denied") # 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: if not user.is_active:
logger.error('The user is inactive') logger.error('ADFS/SAML single sign-on failed: user %s is deactivated.' % user.username)
return HttpResponseForbidden("Permission denied") # 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) auth_login(request, user)
_set_subject_id(request.saml_session, session_info['name_id']) _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 # redirect the user to the view where he came from
default_relay_state = settings.LOGIN_REDIRECT_URL 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): def metadata(request, org_id=None):
org = None
if org_id and int(org_id) > 0: if org_id and int(org_id) > 0:
org_id = int(org_id) org_id = int(org_id)
org = ccnet_api.get_org_by_id(org_id) org = ccnet_api.get_org_by_id(org_id)
if not org: if not org:
logger.error('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('Cannot find an organization related to org_id %s.' % org_id) return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.'))
try: try:
sp_config = get_config(None, request) 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: except Exception as e:
logger.error(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) sp_metadata = entity_descriptor(sp_config)
return HttpResponse( return HttpResponse(
content=str(sp_metadata).encode("utf-8"), content=str(sp_metadata).encode("utf-8"),
@@ -298,16 +421,17 @@ def metadata(request, org_id=None):
@login_required @login_required
def saml2_connect(request, org_id=None): def saml2_connect(request, org_id=None):
org = None
if org_id and int(org_id) > 0: if org_id and int(org_id) > 0:
org_id = int(org_id) org_id = int(org_id)
org = ccnet_api.get_org_by_id(org_id) org = ccnet_api.get_org_by_id(org_id)
if not org: if not org:
logger.error('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('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: 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)) logger.error('User %s does not belong to this organization: %s.' % (request.user.username, org.org_name))
return HttpResponseBadRequest('Failed to bind SAML, please contact admin.') return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.'))
next_url = settings.LOGIN_REDIRECT_URL next_url = settings.LOGIN_REDIRECT_URL
if 'next' in request.GET: if 'next' in request.GET:
@@ -321,9 +445,23 @@ def saml2_connect(request, org_id=None):
try: try:
sp_config = get_config(None, request) 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: except Exception as e:
logger.error(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) saml_client = Saml2Client(sp_config)
session_id, info = saml_client.prepare_for_authenticate(relay_state=next_url) 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_id = int(org_id)
org = ccnet_api.get_org_by_id(org_id) org = ccnet_api.get_org_by_id(org_id)
if not org: 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: 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)) logger.error('User %s does not belong to this organization: %s.' % (request.user.username, org.org_name))
return HttpResponseBadRequest('Failed to disbind SAML, please contact admin.') return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.'))
username = request.user.username username = request.user.username
if request.user.enc_password == '!': 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) profile = Profile.objects.get_profile_by_user(username)
if not profile or not profile.contact_email: 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) SocialAuthUser.objects.delete_by_username_and_provider(username, SAML_PROVIDER_IDENTIFIER)
next_url = request.GET.get(auth.REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL) 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) org = ccnet_api.get_org_by_url_prefix(url_prefix)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
return HttpResponseBadRequest('login failed, please contact admin') return HttpResponseBadRequest(_('Internal server error. Please contact system administrator.'))
if not org: if not org:
logger.error('Cannot find an organization related to url_prefix %s.' % url_prefix) 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) org_id = str(org.org_id)
return HttpResponsePermanentRedirect(request.path.replace(url_prefix, org_id)) return HttpResponsePermanentRedirect(request.path.replace(url_prefix, org_id))

View File

@@ -501,7 +501,7 @@ def multi_adfs_sso(request):
try: try:
org_saml_config = OrgSAMLConfig.objects.get_config_by_domain(domain) org_saml_config = OrgSAMLConfig.objects.get_config_by_domain(domain)
if not org_saml_config: 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) return render(request, template_name, render_data)
if not org_saml_config.domain_verified: if not org_saml_config.domain_verified:
render_data['error_msg'] = \ render_data['error_msg'] = \
@@ -510,7 +510,7 @@ def multi_adfs_sso(request):
org_id = org_saml_config.org_id org_id = org_saml_config.org_id
org = ccnet_api.get_org_by_id(org_id) org = ccnet_api.get_org_by_id(org_id)
if not org: 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) return render(request, template_name, render_data)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)

View File

@@ -258,6 +258,11 @@ class Command(BaseCommand):
return notice 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): def format_sdoc_msg(self, sdoc_queryset, sdoc_notice):
sdoc_obj = sdoc_queryset.filter(uuid=sdoc_notice.doc_uuid).first() sdoc_obj = sdoc_queryset.filter(uuid=sdoc_notice.doc_uuid).first()
if not sdoc_obj: if not sdoc_obj:
@@ -421,6 +426,9 @@ class Command(BaseCommand):
elif notice.is_repo_monitor_msg(): elif notice.is_repo_monitor_msg():
notice = self.format_repo_monitor_msg(notice) 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: if notice is None:
continue continue

View File

@@ -76,6 +76,7 @@ MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'
MSG_TYPE_REPO_TRANSFER = 'repo_transfer' MSG_TYPE_REPO_TRANSFER = 'repo_transfer'
MSG_TYPE_REPO_MINOTOR = 'repo_monitor' MSG_TYPE_REPO_MINOTOR = 'repo_monitor'
MSG_TYPE_DELETED_FILES = 'deleted_files' MSG_TYPE_DELETED_FILES = 'deleted_files'
MSG_TYPE_SAML_SSO_FAILED = 'saml_sso_failed'
USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_' 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, return json.dumps({'org_id': org_id, 'repo_owner': repo_owner,
'repo_id': repo_id, 'repo_name': repo_name}) '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): def get_cache_key_of_unseen_notifications(username):
return normalize_cache_key(username, return normalize_cache_key(username,
USER_NOTIFICATION_COUNT_CACHE_PREFIX) USER_NOTIFICATION_COUNT_CACHE_PREFIX)
@@ -309,6 +315,11 @@ class UserNotificationManager(models.Manager):
return self._add_user_notification( return self._add_user_notification(
to_user, MSG_TYPE_REPO_TRANSFER, detail) 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): class UserNotification(models.Model):
to_user = LowerCaseCharField(db_index=True, max_length=255) to_user = LowerCaseCharField(db_index=True, max_length=255)
@@ -411,6 +422,9 @@ class UserNotification(models.Model):
def is_deleted_files_msg(self): def is_deleted_files_msg(self):
return self.msg_type == MSG_TYPE_DELETED_FILES 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): def user_message_detail_to_dict(self):
"""Parse user message detail, returns dict contains ``message`` and """Parse user message detail, returns dict contains ``message`` and
``msg_from``. ``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.invitations.signals import accept_guest_invitation_successful
from seahub.drafts.signals import comment_draft_successful, \ from seahub.drafts.signals import comment_draft_successful, \
request_reviewer_successful request_reviewer_successful
from seahub.adfs_auth.signals import saml_sso_failed
@receiver(upload_file_successful) @receiver(upload_file_successful)
def add_upload_file_msg_cb(sender, **kwargs): 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) detail = repo_transfer_msg_to_json(org_id, repo_owner, repo_id, repo_name)
UserNotification.objects.add_repo_transfer_msg(to_user, detail) 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)

View File

@@ -65,6 +65,9 @@ You've got {{num}} new notices on {{ site_name }}:
{% elif notice.is_deleted_files_msg %} {% elif notice.is_deleted_files_msg %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">{% blocktrans with repo_url=notice.repo_url repo_name=notice.repo_name %}A large number of files in your library <a href="{{url_base}}{{ repo_url }}">{{ repo_name }}</a> has been deleted recently.{% endblocktrans %}</p> <p style="line-height:1.5; margin:.2em 10px .2em 0;">{% blocktrans with repo_url=notice.repo_url repo_name=notice.repo_name %}A large number of files in your library <a href="{{url_base}}{{ repo_url }}">{{ repo_name }}</a> has been deleted recently.{% endblocktrans %}</p>
{% elif notice.is_saml_sso_error_msg %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">{% blocktrans %}{{notice.error_msg}}{% endblocktrans %}</p>
{% elif notice.is_repo_monitor_msg %} {% elif notice.is_repo_monitor_msg %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;"> <p style="line-height:1.5; margin:.2em 10px .2em 0;">
{% if notice.obj_type == 'file' %} {% if notice.obj_type == 'file' %}

View File

@@ -268,6 +268,13 @@ def update_notice_detail(request, notices):
except Exception as e: except Exception as e:
logger.error(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 return notices

View File

@@ -109,7 +109,7 @@ class OrgVerifyDomain(APIView):
saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id) saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id)
if not saml_config: 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) return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if saml_config.domain != domain: if saml_config.domain != domain: