mirror of https://github.com/jumpserver/jumpserver
commit
d6aad41d05
|
@ -17,8 +17,7 @@ logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class SAML2Backend(ModelBackend):
|
class SAML2Backend(ModelBackend):
|
||||||
@staticmethod
|
def user_can_authenticate(self, user):
|
||||||
def user_can_authenticate(user):
|
|
||||||
is_valid = getattr(user, 'is_valid', None)
|
is_valid = getattr(user, 'is_valid', None)
|
||||||
return is_valid or is_valid is None
|
return is_valid or is_valid is None
|
||||||
|
|
||||||
|
@ -42,9 +41,10 @@ class SAML2Backend(ModelBackend):
|
||||||
log_prompt = "Process authenticate [SAML2AuthCodeBackend]: {}"
|
log_prompt = "Process authenticate [SAML2AuthCodeBackend]: {}"
|
||||||
logger.debug(log_prompt.format('Start'))
|
logger.debug(log_prompt.format('Start'))
|
||||||
if saml_user_data is None:
|
if saml_user_data is None:
|
||||||
logger.debug(log_prompt.format('saml_user_data is missing'))
|
logger.error(log_prompt.format('saml_user_data is missing'))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
logger.debug(log_prompt.format('saml data, {}'.format(saml_user_data)))
|
||||||
username = saml_user_data.get('username')
|
username = saml_user_data.get('username')
|
||||||
if not username:
|
if not username:
|
||||||
logger.debug(log_prompt.format('username is missing'))
|
logger.debug(log_prompt.format('username is missing'))
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import json
|
import copy
|
||||||
import os
|
|
||||||
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.contrib import auth as auth
|
from django.contrib import auth as auth
|
||||||
|
@ -40,18 +39,20 @@ class PrepareRequestMixin:
|
||||||
idp_metadata_url = settings.SAML2_IDP_METADATA_URL
|
idp_metadata_url = settings.SAML2_IDP_METADATA_URL
|
||||||
logger.debug('Start getting IDP configuration')
|
logger.debug('Start getting IDP configuration')
|
||||||
|
|
||||||
|
xml_idp_settings = None
|
||||||
try:
|
try:
|
||||||
xml_idp_settings = IdPMetadataParse.parse(idp_metadata_xml)
|
if idp_metadata_xml.strip():
|
||||||
|
xml_idp_settings = IdPMetadataParse.parse(idp_metadata_xml)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
xml_idp_settings = None
|
|
||||||
logger.warning('Failed to get IDP metadata XML settings, error: %s', str(err))
|
logger.warning('Failed to get IDP metadata XML settings, error: %s', str(err))
|
||||||
|
|
||||||
|
url_idp_settings = None
|
||||||
try:
|
try:
|
||||||
url_idp_settings = IdPMetadataParse.parse_remote(
|
if idp_metadata_url.strip():
|
||||||
idp_metadata_url, timeout=20
|
url_idp_settings = IdPMetadataParse.parse_remote(
|
||||||
)
|
idp_metadata_url, timeout=20
|
||||||
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
url_idp_settings = None
|
|
||||||
logger.warning('Failed to get IDP metadata URL settings, error: %s', str(err))
|
logger.warning('Failed to get IDP metadata URL settings, error: %s', str(err))
|
||||||
|
|
||||||
idp_settings = url_idp_settings or xml_idp_settings
|
idp_settings = url_idp_settings or xml_idp_settings
|
||||||
|
@ -92,14 +93,19 @@ class PrepareRequestMixin:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_advanced_settings():
|
def get_advanced_settings():
|
||||||
other_settings = {}
|
try:
|
||||||
other_settings_path = settings.SAML2_OTHER_SETTINGS_PATH
|
other_settings = dict(settings.SAML2_SP_ADVANCED_SETTINGS)
|
||||||
if os.path.exists(other_settings_path):
|
other_settings = copy.deepcopy(other_settings)
|
||||||
with open(other_settings_path, 'r') as json_data:
|
except Exception as error:
|
||||||
try:
|
logger.error('Get other settings error: %s', error)
|
||||||
other_settings = json.loads(json_data.read())
|
other_settings = {}
|
||||||
except Exception as error:
|
|
||||||
logger.error('Get other settings error: %s', error)
|
security_default = {
|
||||||
|
'wantAttributeStatement': False,
|
||||||
|
'allowRepeatAttributeName': True
|
||||||
|
}
|
||||||
|
security = other_settings.get('security', {})
|
||||||
|
security_default.update(security)
|
||||||
|
|
||||||
default = {
|
default = {
|
||||||
"organization": {
|
"organization": {
|
||||||
|
@ -108,9 +114,10 @@ class PrepareRequestMixin:
|
||||||
"displayname": "JumpServer",
|
"displayname": "JumpServer",
|
||||||
"url": "https://jumpserver.org/"
|
"url": "https://jumpserver.org/"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
default.update(other_settings)
|
default.update(other_settings)
|
||||||
|
default['security'] = security_default
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def get_sp_settings(self):
|
def get_sp_settings(self):
|
||||||
|
@ -157,9 +164,12 @@ class PrepareRequestMixin:
|
||||||
user_attrs = {}
|
user_attrs = {}
|
||||||
real_key_index = len(settings.SITE_URL) + 1
|
real_key_index = len(settings.SITE_URL) + 1
|
||||||
attrs = saml_instance.get_attributes()
|
attrs = saml_instance.get_attributes()
|
||||||
|
valid_attrs = ['username', 'name', 'email', 'comment', 'phone']
|
||||||
|
|
||||||
for attr, value in attrs.items():
|
for attr, value in attrs.items():
|
||||||
attr = attr[real_key_index:]
|
attr = attr[real_key_index:]
|
||||||
|
if attr not in valid_attrs:
|
||||||
|
continue
|
||||||
user_attrs[attr] = self.value_to_str(value)
|
user_attrs[attr] = self.value_to_str(value)
|
||||||
return user_attrs
|
return user_attrs
|
||||||
|
|
||||||
|
@ -167,7 +177,7 @@ class PrepareRequestMixin:
|
||||||
class Saml2AuthRequestView(View, PrepareRequestMixin):
|
class Saml2AuthRequestView(View, PrepareRequestMixin):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
log_prompt = "Process GET requests [SAML2AuthRequestView]: {}"
|
log_prompt = "Process SAML GET requests: {}"
|
||||||
logger.debug(log_prompt.format('Start'))
|
logger.debug(log_prompt.format('Start'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -186,12 +196,12 @@ class Saml2EndSessionView(View, PrepareRequestMixin):
|
||||||
http_method_names = ['get', 'post', ]
|
http_method_names = ['get', 'post', ]
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
log_prompt = "Process GET requests [SAML2EndSessionView]: {}"
|
log_prompt = "Process SAML GET requests: {}"
|
||||||
logger.debug(log_prompt.format('Start'))
|
logger.debug(log_prompt.format('Start'))
|
||||||
return self.post(request)
|
return self.post(request)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
log_prompt = "Process POST requests [SAML2EndSessionView]: {}"
|
log_prompt = "Process SAML POST requests: {}"
|
||||||
logger.debug(log_prompt.format('Start'))
|
logger.debug(log_prompt.format('Start'))
|
||||||
|
|
||||||
logout_url = settings.LOGOUT_REDIRECT_URL or '/'
|
logout_url = settings.LOGOUT_REDIRECT_URL or '/'
|
||||||
|
@ -212,7 +222,7 @@ class Saml2EndSessionView(View, PrepareRequestMixin):
|
||||||
class Saml2AuthCallbackView(View, PrepareRequestMixin):
|
class Saml2AuthCallbackView(View, PrepareRequestMixin):
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
log_prompt = "Process POST requests [SAML2AuthCallbackView]: {}"
|
log_prompt = "Process SAML2 POST requests: {}"
|
||||||
post_data = request.POST
|
post_data = request.POST
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -227,24 +237,25 @@ class Saml2AuthCallbackView(View, PrepareRequestMixin):
|
||||||
|
|
||||||
logger.debug(log_prompt.format('Process saml response'))
|
logger.debug(log_prompt.format('Process saml response'))
|
||||||
saml_instance.process_response(request_id=request_id)
|
saml_instance.process_response(request_id=request_id)
|
||||||
errors = saml_instance.get_errors()
|
errors = saml_instance.get_last_error_reason()
|
||||||
|
|
||||||
if not errors:
|
if errors:
|
||||||
if 'AuthNRequestID' in request.session:
|
logger.error(log_prompt.format('Saml response has error: %s' % str(errors)))
|
||||||
del request.session['AuthNRequestID']
|
return HttpResponseRedirect(settings.AUTH_SAML2_AUTHENTICATION_FAILURE_REDIRECT_URI)
|
||||||
|
|
||||||
logger.debug(log_prompt.format('Process authenticate'))
|
if 'AuthNRequestID' in request.session:
|
||||||
saml_user_data = self.get_attributes(saml_instance)
|
del request.session['AuthNRequestID']
|
||||||
user = auth.authenticate(request=request, saml_user_data=saml_user_data)
|
|
||||||
if user and user.is_valid:
|
|
||||||
logger.debug(log_prompt.format('Login: {}'.format(user)))
|
|
||||||
auth.login(self.request, user)
|
|
||||||
|
|
||||||
logger.debug(log_prompt.format('Redirect'))
|
logger.debug(log_prompt.format('Process authenticate'))
|
||||||
next_url = saml_instance.redirect_to(post_data.get('RelayState', '/'))
|
saml_user_data = self.get_attributes(saml_instance)
|
||||||
return HttpResponseRedirect(next_url)
|
user = auth.authenticate(request=request, saml_user_data=saml_user_data)
|
||||||
logger.error(log_prompt.format('Saml response has error: %s' % str(errors)))
|
if user and user.is_valid:
|
||||||
return HttpResponseRedirect(settings.AUTH_SAML2_AUTHENTICATION_FAILURE_REDIRECT_URI)
|
logger.debug(log_prompt.format('Login: {}'.format(user)))
|
||||||
|
auth.login(self.request, user)
|
||||||
|
|
||||||
|
logger.debug(log_prompt.format('Redirect'))
|
||||||
|
next_url = saml_instance.redirect_to(post_data.get('RelayState', '/'))
|
||||||
|
return HttpResponseRedirect(next_url)
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def dispatch(self, *args, **kwargs):
|
def dispatch(self, *args, **kwargs):
|
||||||
|
|
|
@ -46,57 +46,35 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
|
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
|
||||||
if self.request.GET.get("admin", 0):
|
if self.request.GET.get("admin", 0):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
auth_types = [m for m in self.get_support_auth_methods() if m.get('auto_redirect')]
|
||||||
|
if not auth_types:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 明确直接登录哪个
|
||||||
|
login_to = settings.LOGIN_REDIRECT_TO_BACKEND.upper()
|
||||||
|
if login_to == 'DIRECT':
|
||||||
|
return None
|
||||||
|
|
||||||
|
auth_method = next(filter(lambda x: x['name'] == login_to, auth_types), None)
|
||||||
|
if not auth_method:
|
||||||
|
auth_method = auth_types[0]
|
||||||
|
|
||||||
|
auth_name, redirect_url = auth_method['name'], auth_method['url']
|
||||||
next_url = request.GET.get('next') or '/'
|
next_url = request.GET.get('next') or '/'
|
||||||
auth_type = ''
|
query_string = request.GET.urlencode()
|
||||||
if settings.AUTH_OPENID:
|
redirect_url = '{}?next={}&{}'.format(redirect_url, next_url, query_string)
|
||||||
auth_type = 'OIDC'
|
|
||||||
openid_auth_url = reverse(settings.AUTH_OPENID_AUTH_LOGIN_URL_NAME)
|
|
||||||
openid_auth_url = openid_auth_url + f'?next={next_url}'
|
|
||||||
else:
|
|
||||||
openid_auth_url = None
|
|
||||||
|
|
||||||
if settings.AUTH_CAS:
|
if settings.LOGIN_REDIRECT_MSG_ENABLED:
|
||||||
auth_type = 'CAS'
|
|
||||||
cas_auth_url = reverse(settings.CAS_LOGIN_URL_NAME) + f'?next={next_url}'
|
|
||||||
else:
|
|
||||||
cas_auth_url = None
|
|
||||||
|
|
||||||
if settings.AUTH_SAML2:
|
|
||||||
auth_type = 'saml2'
|
|
||||||
saml2_auth_url = reverse(settings.SAML2_LOGIN_URL_NAME) + f'?next={next_url}'
|
|
||||||
else:
|
|
||||||
saml2_auth_url = None
|
|
||||||
|
|
||||||
if not any([openid_auth_url, cas_auth_url, saml2_auth_url]):
|
|
||||||
return None
|
|
||||||
|
|
||||||
login_redirect = settings.LOGIN_REDIRECT_TO_BACKEND.lower()
|
|
||||||
if login_redirect in ['direct']:
|
|
||||||
return None
|
|
||||||
if login_redirect in ['cas'] and cas_auth_url:
|
|
||||||
auth_url = cas_auth_url
|
|
||||||
elif login_redirect in ['openid', 'oidc'] and openid_auth_url:
|
|
||||||
auth_url = openid_auth_url
|
|
||||||
elif login_redirect in ['saml2'] and saml2_auth_url:
|
|
||||||
auth_url = saml2_auth_url
|
|
||||||
else:
|
|
||||||
auth_url = openid_auth_url or cas_auth_url or saml2_auth_url
|
|
||||||
|
|
||||||
if settings.LOGIN_REDIRECT_TO_BACKEND or not settings.LOGIN_REDIRECT_MSG_ENABLED:
|
|
||||||
redirect_url = auth_url
|
|
||||||
else:
|
|
||||||
message_data = {
|
message_data = {
|
||||||
'title': _('Redirecting'),
|
'title': _('Redirecting'),
|
||||||
'message': _("Redirecting to {} authentication").format(auth_type),
|
'message': _("Redirecting to {} authentication").format(auth_name),
|
||||||
'redirect_url': auth_url,
|
'redirect_url': redirect_url,
|
||||||
'interval': 3,
|
'interval': 3,
|
||||||
'has_cancel': True,
|
'has_cancel': True,
|
||||||
'cancel_url': reverse('authentication:login') + '?admin=1'
|
'cancel_url': reverse('authentication:login') + '?admin=1'
|
||||||
}
|
}
|
||||||
redirect_url = FlashMessageUtil.gen_message_url(message_data)
|
redirect_url = FlashMessageUtil.gen_message_url(message_data)
|
||||||
|
|
||||||
query_string = request.GET.urlencode()
|
|
||||||
redirect_url = "{}&{}".format(redirect_url, query_string)
|
|
||||||
return redirect_url
|
return redirect_url
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -165,25 +143,28 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
'name': 'OpenID',
|
'name': 'OpenID',
|
||||||
'enabled': settings.AUTH_OPENID,
|
'enabled': settings.AUTH_OPENID,
|
||||||
'url': reverse('authentication:openid:login'),
|
'url': reverse('authentication:openid:login'),
|
||||||
'logo': static('img/login_oidc_logo.png')
|
'logo': static('img/login_oidc_logo.png'),
|
||||||
|
'auto_redirect': True # 是否支持自动重定向
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CAS',
|
'name': 'CAS',
|
||||||
'enabled': settings.AUTH_CAS,
|
'enabled': settings.AUTH_CAS,
|
||||||
'url': reverse('authentication:cas:cas-login'),
|
'url': reverse('authentication:cas:cas-login'),
|
||||||
'logo': static('img/login_cas_logo.png')
|
'logo': static('img/login_cas_logo.png'),
|
||||||
|
'auto_redirect': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'SAML2',
|
'name': 'SAML2',
|
||||||
'enabled': settings.AUTH_SAML2,
|
'enabled': settings.AUTH_SAML2,
|
||||||
'url': reverse('authentication:saml2:saml2-login'),
|
'url': reverse('authentication:saml2:saml2-login'),
|
||||||
'logo': static('img/login_cas_logo.png')
|
'logo': static('img/login_cas_logo.png'),
|
||||||
|
'auto_redirect': True
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('WeCom'),
|
'name': _('WeCom'),
|
||||||
'enabled': settings.AUTH_WECOM,
|
'enabled': settings.AUTH_WECOM,
|
||||||
'url': reverse('authentication:wecom-qr-login'),
|
'url': reverse('authentication:wecom-qr-login'),
|
||||||
'logo': static('img/login_wecom_logo.png')
|
'logo': static('img/login_wecom_logo.png'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('DingTalk'),
|
'name': _('DingTalk'),
|
||||||
|
|
|
@ -29,7 +29,7 @@ def set_default(data: dict, default: dict):
|
||||||
|
|
||||||
|
|
||||||
class DictWrapper:
|
class DictWrapper:
|
||||||
def __init__(self, data:dict):
|
def __init__(self, data: dict):
|
||||||
self.raw_data = data
|
self.raw_data = data
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
|
@ -51,7 +51,7 @@ class DictWrapper:
|
||||||
return str(self.raw_data)
|
return str(self.raw_data)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self.raw_data)
|
return repr(self.raw_data)
|
||||||
|
|
||||||
|
|
||||||
def as_request(func):
|
def as_request(func):
|
||||||
|
|
|
@ -124,6 +124,9 @@ class WeCom(RequestMixin):
|
||||||
return users
|
return users
|
||||||
self._requests.check_errcode_is_0(data)
|
self._requests.check_errcode_is_0(data)
|
||||||
|
|
||||||
|
if 'invaliduser' not in data:
|
||||||
|
return ()
|
||||||
|
|
||||||
invaliduser = data['invaliduser']
|
invaliduser = data['invaliduser']
|
||||||
if not invaliduser:
|
if not invaliduser:
|
||||||
return ()
|
return ()
|
||||||
|
|
|
@ -281,7 +281,6 @@ def get_docker_mem_usage_if_limit():
|
||||||
return ((usage_in_bytes - inactive_file) / limit_in_bytes) * 100
|
return ((usage_in_bytes - inactive_file) / limit_in_bytes) * 100
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f'Get memory usage by docker limit: {e}')
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -234,7 +234,18 @@ class Config(dict):
|
||||||
'SAML2_LOGOUT_COMPLETELY': True,
|
'SAML2_LOGOUT_COMPLETELY': True,
|
||||||
'AUTH_SAML2_ALWAYS_UPDATE_USER': True,
|
'AUTH_SAML2_ALWAYS_UPDATE_USER': True,
|
||||||
'SAML2_RENAME_ATTRIBUTES': {'uid': 'username', 'email': 'email'},
|
'SAML2_RENAME_ATTRIBUTES': {'uid': 'username', 'email': 'email'},
|
||||||
'SAML2_OTHER_SETTINGS_PATH': '',
|
'SAML2_SP_ADVANCED_SETTINGS': {
|
||||||
|
"organization": {
|
||||||
|
"en": {
|
||||||
|
"name": "JumpServer",
|
||||||
|
"displayname": "JumpServer",
|
||||||
|
"url": "https://jumpserver.org/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strict": True,
|
||||||
|
"security": {
|
||||||
|
}
|
||||||
|
},
|
||||||
'SAML2_IDP_METADATA_URL': '',
|
'SAML2_IDP_METADATA_URL': '',
|
||||||
'SAML2_IDP_METADATA_XML': '',
|
'SAML2_IDP_METADATA_XML': '',
|
||||||
'SAML2_SP_KEY_CONTENT': '',
|
'SAML2_SP_KEY_CONTENT': '',
|
||||||
|
|
|
@ -129,7 +129,7 @@ AUTH_SAML2_AUTHENTICATION_FAILURE_REDIRECT_URI = CONFIG.AUTH_SAML2_AUTHENTICATIO
|
||||||
AUTH_SAML2_ALWAYS_UPDATE_USER = CONFIG.AUTH_SAML2_ALWAYS_UPDATE_USER
|
AUTH_SAML2_ALWAYS_UPDATE_USER = CONFIG.AUTH_SAML2_ALWAYS_UPDATE_USER
|
||||||
SAML2_LOGOUT_COMPLETELY = CONFIG.SAML2_LOGOUT_COMPLETELY
|
SAML2_LOGOUT_COMPLETELY = CONFIG.SAML2_LOGOUT_COMPLETELY
|
||||||
SAML2_RENAME_ATTRIBUTES = CONFIG.SAML2_RENAME_ATTRIBUTES
|
SAML2_RENAME_ATTRIBUTES = CONFIG.SAML2_RENAME_ATTRIBUTES
|
||||||
SAML2_OTHER_SETTINGS_PATH = CONFIG.SAML2_OTHER_SETTINGS_PATH
|
SAML2_SP_ADVANCED_SETTINGS = CONFIG.SAML2_SP_ADVANCED_SETTINGS
|
||||||
SAML2_LOGIN_URL_NAME = "authentication:saml2:saml2-login"
|
SAML2_LOGIN_URL_NAME = "authentication:saml2:saml2-login"
|
||||||
SAML2_LOGOUT_URL_NAME = "authentication:saml2:saml2-logout"
|
SAML2_LOGOUT_URL_NAME = "authentication:saml2:saml2-logout"
|
||||||
|
|
||||||
|
|
|
@ -2857,7 +2857,7 @@ msgstr "服务端地址"
|
||||||
|
|
||||||
#: settings/serializers/auth/cas.py:13
|
#: settings/serializers/auth/cas.py:13
|
||||||
msgid "Proxy server url"
|
msgid "Proxy server url"
|
||||||
msgstr "代理服务地址"
|
msgstr "回调地址"
|
||||||
|
|
||||||
#: settings/serializers/auth/cas.py:14 settings/serializers/auth/saml2.py:29
|
#: settings/serializers/auth/cas.py:14 settings/serializers/auth/saml2.py:29
|
||||||
msgid "Logout completely"
|
msgid "Logout completely"
|
||||||
|
|
|
@ -72,7 +72,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
|
||||||
self.org = org
|
self.org = org
|
||||||
|
|
||||||
def get_key_suffix(self):
|
def get_key_suffix(self):
|
||||||
return f'<org:{self.org.id}>'
|
return f'org_{self.org.id}'
|
||||||
|
|
||||||
def get_current_org(self):
|
def get_current_org(self):
|
||||||
return self.org
|
return self.org
|
||||||
|
|
|
@ -17,6 +17,9 @@ class SAML2SettingSerializer(serializers.Serializer):
|
||||||
SAML2_IDP_METADATA_XML = serializers.CharField(
|
SAML2_IDP_METADATA_XML = serializers.CharField(
|
||||||
allow_blank=True, required=False, label=_('IDP Metadata XML')
|
allow_blank=True, required=False, label=_('IDP Metadata XML')
|
||||||
)
|
)
|
||||||
|
SAML2_SP_ADVANCED_SETTINGS = serializers.JSONField(
|
||||||
|
required=False, label=_('SP ADVANCED SETTINGS')
|
||||||
|
)
|
||||||
SAML2_SP_KEY_CONTENT = serializers.CharField(
|
SAML2_SP_KEY_CONTENT = serializers.CharField(
|
||||||
allow_blank=True, required=False,
|
allow_blank=True, required=False,
|
||||||
write_only=True, label=_('SP Private Key')
|
write_only=True, label=_('SP Private Key')
|
||||||
|
|
|
@ -121,8 +121,11 @@ class CommandViewSet(JMSBulkModelViewSet):
|
||||||
qs = storage.get_command_queryset()
|
qs = storage.get_command_queryset()
|
||||||
commands = self.filter_queryset(qs)
|
commands = self.filter_queryset(qs)
|
||||||
merged_commands.extend(commands[:]) # ES 默认只取 10 条数据
|
merged_commands.extend(commands[:]) # ES 默认只取 10 条数据
|
||||||
|
order = self.request.query_params.get('order', None)
|
||||||
merged_commands.sort(key=lambda command: command.timestamp, reverse=True)
|
if order == 'timestamp':
|
||||||
|
merged_commands.sort(key=lambda command: command.timestamp)
|
||||||
|
else:
|
||||||
|
merged_commands.sort(key=lambda command: command.timestamp, reverse=True)
|
||||||
page = self.paginate_queryset(merged_commands)
|
page = self.paginate_queryset(merged_commands)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
serializer = self.get_serializer(page, many=True)
|
serializer = self.get_serializer(page, many=True)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from common.mixins.models import CommonModelMixin
|
||||||
from common.db.encoder import ModelJSONFieldEncoder
|
from common.db.encoder import ModelJSONFieldEncoder
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org, tmp_to_org
|
||||||
from ..const import TicketType, TicketApprovalLevel, TicketApprovalStrategy
|
from ..const import TicketType, TicketApprovalLevel, TicketApprovalStrategy
|
||||||
from ..signals import post_or_update_change_ticket_flow_approval
|
from ..signals import post_or_update_change_ticket_flow_approval
|
||||||
|
|
||||||
|
@ -64,9 +64,13 @@ class TicketFlow(CommonModelMixin, OrgModelMixin):
|
||||||
return '{}'.format(self.type)
|
return '{}'.format(self.type)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_org_related_flows(cls):
|
def get_org_related_flows(cls, org_id=None):
|
||||||
flows = cls.objects.all()
|
if org_id:
|
||||||
|
with tmp_to_org(org_id):
|
||||||
|
flows = cls.objects.all()
|
||||||
|
else:
|
||||||
|
flows = cls.objects.all()
|
||||||
cur_flow_types = flows.values_list('type', flat=True)
|
cur_flow_types = flows.values_list('type', flat=True)
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
diff_global_flows = cls.objects.exclude(type__in=cur_flow_types).filter(org_id=Organization.ROOT_ID)
|
diff_global_flows = cls.objects.exclude(type__in=cur_flow_types)
|
||||||
return flows | diff_global_flows
|
return flows | diff_global_flows
|
||||||
|
|
|
@ -108,7 +108,8 @@ class TicketApplySerializer(TicketSerializer):
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
ticket_type = attrs.get('type')
|
ticket_type = attrs.get('type')
|
||||||
flow = TicketFlow.get_org_related_flows().filter(type=ticket_type).first()
|
org_id = attrs.get('org_id')
|
||||||
|
flow = TicketFlow.get_org_related_flows(org_id=org_id).filter(type=ticket_type).first()
|
||||||
if flow:
|
if flow:
|
||||||
attrs['flow'] = flow
|
attrs['flow'] = flow
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -62,7 +62,7 @@ pytz==2018.3
|
||||||
PyYAML==5.4
|
PyYAML==5.4
|
||||||
redis==3.5.3
|
redis==3.5.3
|
||||||
requests==2.25.1
|
requests==2.25.1
|
||||||
jms-storage==0.0.39
|
jms-storage==0.0.40
|
||||||
s3transfer==0.5.0
|
s3transfer==0.5.0
|
||||||
simplejson==3.13.2
|
simplejson==3.13.2
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
|
|
Loading…
Reference in New Issue