mirror of https://github.com/jumpserver/jumpserver
commit
31de9375e7
|
@ -17,6 +17,7 @@ ARG DEPENDENCIES=" \
|
|||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
libaio-dev \
|
||||
openssh-client \
|
||||
sshpass"
|
||||
|
||||
ARG TOOLS=" \
|
||||
|
|
|
@ -16,7 +16,7 @@ from .. import const
|
|||
|
||||
__all__ = [
|
||||
'AppSerializer', 'MiniAppSerializer', 'AppSerializerMixin',
|
||||
'AppAccountSerializer', 'AppAccountSecretSerializer'
|
||||
'AppAccountSerializer', 'AppAccountSecretSerializer', 'AppAccountBackUpSerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -32,21 +32,23 @@ class AppSerializerMixin(serializers.Serializer):
|
|||
return instance
|
||||
|
||||
def get_attrs_serializer(self):
|
||||
default_serializer = serializers.Serializer(read_only=True)
|
||||
instance = self.app
|
||||
if instance:
|
||||
_type = instance.type
|
||||
_category = instance.category
|
||||
else:
|
||||
_type = self.context['request'].query_params.get('type')
|
||||
_category = self.context['request'].query_params.get('category')
|
||||
if _type:
|
||||
if isinstance(self, AppAccountSecretSerializer):
|
||||
serializer_class = type_secret_serializer_classes_mapping.get(_type)
|
||||
tp = getattr(self, 'tp', None)
|
||||
default_serializer = serializers.Serializer(read_only=True)
|
||||
if not tp:
|
||||
if instance:
|
||||
tp = instance.type
|
||||
category = instance.category
|
||||
else:
|
||||
serializer_class = type_serializer_classes_mapping.get(_type)
|
||||
elif _category:
|
||||
serializer_class = category_serializer_classes_mapping.get(_category)
|
||||
tp = self.context['request'].query_params.get('type')
|
||||
category = self.context['request'].query_params.get('category')
|
||||
if tp:
|
||||
if isinstance(self, AppAccountBackUpSerializer):
|
||||
serializer_class = type_secret_serializer_classes_mapping.get(tp)
|
||||
else:
|
||||
serializer_class = type_serializer_classes_mapping.get(tp)
|
||||
elif category:
|
||||
serializer_class = category_serializer_classes_mapping.get(category)
|
||||
else:
|
||||
serializer_class = default_serializer
|
||||
|
||||
|
@ -154,11 +156,6 @@ class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResou
|
|||
|
||||
class AppAccountSecretSerializer(SecretReadableMixin, AppAccountSerializer):
|
||||
class Meta(AppAccountSerializer.Meta):
|
||||
fields_backup = [
|
||||
'id', 'app_display', 'attrs', 'username', 'password', 'private_key',
|
||||
'public_key', 'date_created', 'date_updated', 'version'
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': False},
|
||||
'private_key': {'write_only': False},
|
||||
|
@ -166,3 +163,22 @@ class AppAccountSecretSerializer(SecretReadableMixin, AppAccountSerializer):
|
|||
'app_display': {'label': _('Application display')},
|
||||
'systemuser_display': {'label': _('System User')}
|
||||
}
|
||||
|
||||
|
||||
class AppAccountBackUpSerializer(AppAccountSecretSerializer):
|
||||
class Meta(AppAccountSecretSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'app_display', 'attrs', 'username', 'password', 'private_key',
|
||||
'public_key', 'date_created', 'date_updated', 'version'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.tp = kwargs.pop('tp', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
return queryset
|
||||
|
||||
def to_representation(self, instance):
|
||||
return super(AppAccountSerializer, self).to_representation(instance)
|
||||
|
|
|
@ -13,3 +13,14 @@ class DBSerializer(serializers.Serializer):
|
|||
database = serializers.CharField(
|
||||
max_length=128, required=True, allow_null=True, label=_('Database')
|
||||
)
|
||||
use_ssl = serializers.BooleanField(default=False, label=_('Use SSL'))
|
||||
ca_cert = serializers.CharField(
|
||||
required=False, allow_null=True, label=_('CA certificate')
|
||||
)
|
||||
client_cert = serializers.CharField(
|
||||
required=False, allow_null=True, label=_('Client certificate file')
|
||||
)
|
||||
cert_key = serializers.CharField(
|
||||
required=False, allow_null=True, label=_('Certificate key file')
|
||||
)
|
||||
allow_invalid_cert = serializers.BooleanField(default=False, label=_('Allow invalid cert'))
|
||||
|
|
|
@ -76,10 +76,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
|
||||
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
||||
class Meta(AccountSerializer.Meta):
|
||||
fields_backup = [
|
||||
'hostname', 'ip', 'platform', 'protocols', 'username', 'password',
|
||||
'private_key', 'public_key', 'date_created', 'date_updated', 'version'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': False},
|
||||
'private_key': {'write_only': False},
|
||||
|
@ -88,6 +84,22 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
|||
}
|
||||
|
||||
|
||||
class AccountBackUpSerializer(AccountSecretSerializer):
|
||||
class Meta(AccountSecretSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'hostname', 'ip', 'username', 'password',
|
||||
'private_key', 'public_key', 'date_created',
|
||||
'date_updated', 'version'
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
return queryset
|
||||
|
||||
def to_representation(self, instance):
|
||||
return super(AccountSerializer, self).to_representation(instance)
|
||||
|
||||
|
||||
class AccountTaskSerializer(serializers.Serializer):
|
||||
ACTION_CHOICES = (
|
||||
('test', 'test'),
|
||||
|
|
|
@ -4,15 +4,16 @@ from openpyxl import Workbook
|
|||
from collections import defaultdict, OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from assets.models import AuthBook
|
||||
from assets.serializers import AccountSecretSerializer
|
||||
from assets.models import AuthBook, SystemUser, Asset
|
||||
from assets.serializers import AccountBackUpSerializer
|
||||
from assets.notifications import AccountBackupExecutionTaskMsg
|
||||
from applications.models import Account
|
||||
from applications.models import Account, Application
|
||||
from applications.const import AppType
|
||||
from applications.serializers import AppAccountSecretSerializer
|
||||
from applications.serializers import AppAccountBackUpSerializer
|
||||
from users.models import User
|
||||
from common.utils import get_logger
|
||||
from common.utils.timezone import local_now_display
|
||||
|
@ -38,7 +39,7 @@ class BaseAccountHandler:
|
|||
@classmethod
|
||||
def get_header_fields(cls, serializer: serializers.Serializer):
|
||||
try:
|
||||
backup_fields = getattr(serializer, 'Meta').fields_backup
|
||||
backup_fields = getattr(serializer, 'Meta').fields
|
||||
except AttributeError:
|
||||
backup_fields = serializer.fields.keys()
|
||||
header_fields = {}
|
||||
|
@ -51,17 +52,41 @@ class BaseAccountHandler:
|
|||
header_fields[field] = str(v.label)
|
||||
return header_fields
|
||||
|
||||
@staticmethod
|
||||
def load_auth(tp, value, system_user):
|
||||
if value:
|
||||
return value
|
||||
if system_user:
|
||||
return getattr(system_user, tp, '')
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
def create_row(cls, account, serializer_cls, header_fields=None):
|
||||
serializer = serializer_cls(account)
|
||||
if not header_fields:
|
||||
header_fields = cls.get_header_fields(serializer)
|
||||
data = cls.unpack_data(serializer.data)
|
||||
def replace_auth(cls, account, system_user_dict):
|
||||
system_user = system_user_dict.get(account.systemuser_id)
|
||||
account.username = cls.load_auth('username', account.username, system_user)
|
||||
account.password = cls.load_auth('password', account.password, system_user)
|
||||
account.private_key = cls.load_auth('private_key', account.private_key, system_user)
|
||||
account.public_key = cls.load_auth('public_key', account.public_key, system_user)
|
||||
return account
|
||||
|
||||
@classmethod
|
||||
def create_row(cls, data, header_fields):
|
||||
data = cls.unpack_data(data)
|
||||
row_dict = {}
|
||||
for field, header_name in header_fields.items():
|
||||
row_dict[header_name] = str(data[field])
|
||||
row_dict[header_name] = str(data.get(field, field))
|
||||
return row_dict
|
||||
|
||||
@classmethod
|
||||
def add_rows(cls, data, header_fields, sheet):
|
||||
data_map = defaultdict(list)
|
||||
for i in data:
|
||||
row = cls.create_row(i, header_fields)
|
||||
if sheet not in data_map:
|
||||
data_map[sheet].append(list(row.keys()))
|
||||
data_map[sheet].append(list(row.values()))
|
||||
return data_map
|
||||
|
||||
|
||||
class AssetAccountHandler(BaseAccountHandler):
|
||||
@staticmethod
|
||||
|
@ -72,22 +97,27 @@ class AssetAccountHandler(BaseAccountHandler):
|
|||
return filename
|
||||
|
||||
@classmethod
|
||||
def create_data_map(cls):
|
||||
data_map = defaultdict(list)
|
||||
def replace_account_info(cls, account, asset_dict, system_user_dict):
|
||||
asset = asset_dict.get(account.asset_id)
|
||||
account.ip = asset.ip if asset else ''
|
||||
account.hostname = asset.hostname if asset else ''
|
||||
account = cls.replace_auth(account, system_user_dict)
|
||||
return account
|
||||
|
||||
@classmethod
|
||||
def create_data_map(cls, system_user_dict):
|
||||
sheet_name = AuthBook._meta.verbose_name
|
||||
assets = Asset.objects.only('id', 'hostname', 'ip')
|
||||
asset_dict = {asset.id: asset for asset in assets}
|
||||
accounts = AuthBook.objects.all()
|
||||
if not accounts.exists():
|
||||
return
|
||||
|
||||
accounts = AuthBook.get_queryset().select_related('systemuser')
|
||||
if not accounts.first():
|
||||
return data_map
|
||||
|
||||
header_fields = cls.get_header_fields(AccountSecretSerializer(accounts.first()))
|
||||
header_fields = cls.get_header_fields(AccountBackUpSerializer(accounts.first()))
|
||||
for account in accounts:
|
||||
account.load_auth()
|
||||
row = cls.create_row(account, AccountSecretSerializer, header_fields)
|
||||
if sheet_name not in data_map:
|
||||
data_map[sheet_name].append(list(row.keys()))
|
||||
data_map[sheet_name].append(list(row.values()))
|
||||
|
||||
cls.replace_account_info(account, asset_dict, system_user_dict)
|
||||
data = AccountBackUpSerializer(accounts, many=True).data
|
||||
data_map = cls.add_rows(data, header_fields, sheet_name)
|
||||
logger.info('\n\033[33m- 共收集 {} 条资产账号\033[0m'.format(accounts.count()))
|
||||
return data_map
|
||||
|
||||
|
@ -101,18 +131,36 @@ class AppAccountHandler(BaseAccountHandler):
|
|||
return filename
|
||||
|
||||
@classmethod
|
||||
def create_data_map(cls):
|
||||
data_map = defaultdict(list)
|
||||
accounts = Account.get_queryset().select_related('systemuser')
|
||||
for account in accounts:
|
||||
account.load_auth()
|
||||
app_type = account.type
|
||||
def replace_account_info(cls, account, app_dict, system_user_dict):
|
||||
app = app_dict.get(account.app_id)
|
||||
account.type = app.type if app else ''
|
||||
account.app_display = app.name if app else ''
|
||||
account.category = app.category if app else ''
|
||||
account = cls.replace_auth(account, system_user_dict)
|
||||
return account
|
||||
|
||||
@classmethod
|
||||
def create_data_map(cls, system_user_dict):
|
||||
apps = Application.objects.only('id', 'type', 'name', 'category')
|
||||
app_dict = {app.id: app for app in apps}
|
||||
qs = Account.objects.all().annotate(app_type=F('app__type'))
|
||||
if not qs.exists():
|
||||
return
|
||||
|
||||
account_type_map = defaultdict(list)
|
||||
for i in qs:
|
||||
account_type_map[i.app_type].append(i)
|
||||
data_map = {}
|
||||
for app_type, accounts in account_type_map.items():
|
||||
sheet_name = AppType.get_label(app_type)
|
||||
row = cls.create_row(account, AppAccountSecretSerializer)
|
||||
if sheet_name not in data_map:
|
||||
data_map[sheet_name].append(list(row.keys()))
|
||||
data_map[sheet_name].append(list(row.values()))
|
||||
logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(accounts.count()))
|
||||
header_fields = cls.get_header_fields(AppAccountBackUpSerializer(tp=app_type))
|
||||
if not accounts:
|
||||
continue
|
||||
for account in accounts:
|
||||
cls.replace_account_info(account, app_dict, system_user_dict)
|
||||
data = AppAccountBackUpSerializer(accounts, many=True, app_type=app_type).data
|
||||
data_map.update(cls.add_rows(data, header_fields, sheet_name))
|
||||
logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(qs.count()))
|
||||
return data_map
|
||||
|
||||
|
||||
|
@ -137,12 +185,16 @@ class AccountBackupHandler:
|
|||
# Print task start date
|
||||
time_start = time.time()
|
||||
files = []
|
||||
system_user_qs = SystemUser.objects.only(
|
||||
'id', 'username', 'password', 'private_key', 'public_key'
|
||||
)
|
||||
system_user_dict = {i.id: i for i in system_user_qs}
|
||||
for account_type in self.execution.types:
|
||||
handler = handler_map.get(account_type)
|
||||
if not handler:
|
||||
continue
|
||||
|
||||
data_map = handler.create_data_map()
|
||||
data_map = handler.create_data_map(system_user_dict)
|
||||
if not data_map:
|
||||
continue
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
from django.conf import settings
|
||||
from django.utils.module_loading import import_string
|
||||
from common.utils import get_logger
|
||||
from django.contrib.auth import get_user_model
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
|
||||
from .base import JMSModelBackend
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
custom_authenticate_method = None
|
||||
|
||||
if settings.AUTH_CUSTOM:
|
||||
""" 保证自定义认证方法在服务运行时不能被更改,只在第一次调用时加载一次 """
|
||||
try:
|
||||
custom_auth_method_path = 'data.auth.main.authenticate'
|
||||
custom_authenticate_method = import_string(custom_auth_method_path)
|
||||
except Exception as e:
|
||||
logger.warning('Import custom auth method failed: {}, Maybe not enabled'.format(e))
|
||||
|
||||
|
||||
class CustomAuthBackend(JMSModelBackend):
|
||||
|
||||
def is_enabled(self):
|
||||
return settings.AUTH_CUSTOM and callable(custom_authenticate_method)
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_user_from_userinfo(userinfo: dict):
|
||||
username = userinfo['username']
|
||||
attrs = ['name', 'username', 'email', 'is_active']
|
||||
defaults = {attr: userinfo[attr] for attr in attrs}
|
||||
user, created = get_user_model().objects.get_or_create(
|
||||
username=username, defaults=defaults
|
||||
)
|
||||
return user, created
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
try:
|
||||
userinfo: dict = custom_authenticate_method(
|
||||
username=username, password=password, **kwargs
|
||||
)
|
||||
user, created = self.get_or_create_user_from_userinfo(userinfo)
|
||||
except Exception as e:
|
||||
logger.error('Custom authenticate error: {}'.format(e))
|
||||
return None
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.info(f'Custom authenticate success: {user.username}')
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_CUSTOM
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.info(f'Custom authenticate failed: {user.username}')
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason=_('User invalid, disabled or expired'),
|
||||
backend=settings.AUTH_BACKEND_CUSTOM
|
||||
)
|
||||
return None
|
|
@ -10,11 +10,11 @@ from django.urls import reverse
|
|||
from common.utils import get_logger
|
||||
from users.utils import construct_user_email
|
||||
from authentication.utils import build_absolute_uri
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from common.exceptions import JMSException
|
||||
|
||||
from .signals import (
|
||||
oauth2_create_or_update_user, oauth2_user_login_failed,
|
||||
oauth2_user_login_success
|
||||
oauth2_create_or_update_user
|
||||
)
|
||||
from ..base import JMSModelBackend
|
||||
|
||||
|
@ -145,13 +145,17 @@ class OAuth2Backend(JMSModelBackend):
|
|||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OAuth2 user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => oauth2 user login success'))
|
||||
oauth2_user_login_success.send(sender=self.__class__, request=request, user=user)
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OAUTH2
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OAuth2 user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => oauth2 user login failed'))
|
||||
oauth2_user_login_failed.send(
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason=_('User invalid, disabled or expired')
|
||||
reason=_('User invalid, disabled or expired'),
|
||||
backend=settings.AUTH_BACKEND_OAUTH2
|
||||
)
|
||||
return None
|
||||
|
|
|
@ -4,6 +4,4 @@ from django.dispatch import Signal
|
|||
oauth2_create_or_update_user = Signal(
|
||||
providing_args=['request', 'user', 'created', 'name', 'username', 'email']
|
||||
)
|
||||
oauth2_user_login_success = Signal(providing_args=['request', 'user'])
|
||||
oauth2_user_login_failed = Signal(providing_args=['request', 'username', 'reason'])
|
||||
|
||||
|
|
|
@ -7,5 +7,6 @@ from . import views
|
|||
|
||||
urlpatterns = [
|
||||
path('login/', views.OAuth2AuthRequestView.as_view(), name='login'),
|
||||
path('callback/', views.OAuth2AuthCallbackView.as_view(), name='login-callback')
|
||||
path('callback/', views.OAuth2AuthCallbackView.as_view(), name='login-callback'),
|
||||
path('logout/', views.OAuth2EndSessionView.as_view(), name='logout')
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.views import View
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login
|
||||
from django.contrib import auth
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
|
@ -48,7 +48,7 @@ class OAuth2AuthCallbackView(View):
|
|||
user = authenticate(code=callback_params['code'], request=request)
|
||||
if user and user.is_valid:
|
||||
logger.debug(log_prompt.format('Login: {}'.format(user)))
|
||||
login(self.request, user)
|
||||
auth.login(self.request, user)
|
||||
logger.debug(log_prompt.format('Redirect'))
|
||||
return HttpResponseRedirect(
|
||||
settings.AUTH_OAUTH2_AUTHENTICATION_REDIRECT_URI
|
||||
|
@ -56,3 +56,33 @@ class OAuth2AuthCallbackView(View):
|
|||
|
||||
logger.debug(log_prompt.format('Redirect'))
|
||||
return HttpResponseRedirect(settings.AUTH_OAUTH2_AUTHENTICATION_FAILURE_REDIRECT_URI)
|
||||
|
||||
|
||||
class OAuth2EndSessionView(View):
|
||||
http_method_names = ['get', 'post', ]
|
||||
|
||||
def get(self, request):
|
||||
""" Processes GET requests. """
|
||||
log_prompt = "Process GET requests [OAuth2EndSessionView]: {}"
|
||||
logger.debug(log_prompt.format('Start'))
|
||||
return self.post(request)
|
||||
|
||||
def post(self, request):
|
||||
""" Processes POST requests. """
|
||||
log_prompt = "Process POST requests [OAuth2EndSessionView]: {}"
|
||||
logger.debug(log_prompt.format('Start'))
|
||||
|
||||
logout_url = settings.LOGOUT_REDIRECT_URL or '/'
|
||||
|
||||
# Log out the current user.
|
||||
if request.user.is_authenticated:
|
||||
logger.debug(log_prompt.format('Log out the current user: {}'.format(request.user)))
|
||||
auth.logout(request)
|
||||
|
||||
if settings.AUTH_OAUTH2_LOGOUT_COMPLETELY:
|
||||
logger.debug(log_prompt.format('Log out OAUTH2 platform user session synchronously'))
|
||||
next_url = settings.AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT
|
||||
return HttpResponseRedirect(next_url)
|
||||
|
||||
logger.debug(log_prompt.format('Redirect'))
|
||||
return HttpResponseRedirect(logout_url)
|
||||
|
|
|
@ -26,8 +26,9 @@ from ..base import JMSBaseAuthBackend
|
|||
from .utils import validate_and_return_id_token
|
||||
from .decorator import ssl_verification
|
||||
from .signals import (
|
||||
openid_create_or_update_user, openid_user_login_failed, openid_user_login_success
|
||||
openid_create_or_update_user
|
||||
)
|
||||
from authentication.signals import user_auth_success, user_auth_failed
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -213,14 +214,18 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
|||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OpenID user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login success'))
|
||||
openid_user_login_success.send(sender=self.__class__, request=request, user=user)
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OIDC_CODE
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OpenID user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason="User is invalid"
|
||||
reason="User is invalid", backend=settings.AUTH_BACKEND_OIDC_CODE
|
||||
|
||||
)
|
||||
return None
|
||||
|
||||
|
@ -271,8 +276,9 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
|||
"content is: {}, error is: {}".format(token_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -299,8 +305,9 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
|||
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -312,13 +319,16 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
|||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OpenID user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login success'))
|
||||
openid_user_login_success.send(
|
||||
sender=self.__class__, request=request, user=user
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OpenID user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason="User is invalid"
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason="User is invalid",
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return None
|
||||
|
|
|
@ -13,6 +13,4 @@ from django.dispatch import Signal
|
|||
openid_create_or_update_user = Signal(
|
||||
providing_args=['request', 'user', 'created', 'name', 'username', 'email']
|
||||
)
|
||||
openid_user_login_success = Signal(providing_args=['request', 'user'])
|
||||
openid_user_login_failed = Signal(providing_args=['request', 'username', 'reason'])
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ from django.db import transaction
|
|||
from common.utils import get_logger
|
||||
from authentication.errors import reason_choices, reason_user_invalid
|
||||
from .signals import (
|
||||
saml2_user_authenticated, saml2_user_authentication_failed,
|
||||
saml2_create_or_update_user
|
||||
)
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from ..base import JMSModelBackend
|
||||
|
||||
__all__ = ['SAML2Backend']
|
||||
|
@ -55,14 +55,16 @@ class SAML2Backend(JMSModelBackend):
|
|||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('SAML2 user login success'))
|
||||
saml2_user_authenticated.send(
|
||||
sender=self, request=request, user=user, created=created
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user, created=created,
|
||||
backend=settings.AUTH_BACKEND_SAML2
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('SAML2 user login failed'))
|
||||
saml2_user_authentication_failed.send(
|
||||
sender=self, request=request, username=username,
|
||||
reason=reason_choices.get(reason_user_invalid)
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username,
|
||||
reason=reason_choices.get(reason_user_invalid),
|
||||
backend=settings.AUTH_BACKEND_SAML2
|
||||
)
|
||||
return None
|
||||
|
|
|
@ -2,5 +2,3 @@ from django.dispatch import Signal
|
|||
|
||||
|
||||
saml2_create_or_update_user = Signal(providing_args=('user', 'created', 'request', 'attrs'))
|
||||
saml2_user_authenticated = Signal(providing_args=('user', 'created', 'request'))
|
||||
saml2_user_authentication_failed = Signal(providing_args=('request', 'username', 'reason'))
|
||||
|
|
|
@ -3,7 +3,7 @@ import copy
|
|||
from urllib import parse
|
||||
|
||||
from django.views import View
|
||||
from django.contrib import auth as auth
|
||||
from django.contrib import auth
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
|
|
@ -7,16 +7,7 @@ from django.dispatch import receiver
|
|||
from django_cas_ng.signals import cas_user_authenticated
|
||||
|
||||
from apps.jumpserver.settings.auth import AUTHENTICATION_BACKENDS_THIRD_PARTY
|
||||
from authentication.backends.oidc.signals import (
|
||||
openid_user_login_failed, openid_user_login_success
|
||||
)
|
||||
from authentication.backends.saml2.signals import (
|
||||
saml2_user_authenticated, saml2_user_authentication_failed
|
||||
)
|
||||
from authentication.backends.oauth2.signals import (
|
||||
oauth2_user_login_failed, oauth2_user_login_success
|
||||
)
|
||||
from .signals import post_auth_success, post_auth_failed
|
||||
from .signals import post_auth_success, post_auth_failed, user_auth_failed, user_auth_success
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
|
@ -29,7 +20,8 @@ def on_user_auth_login_success(sender, user, request, **kwargs):
|
|||
and user.mfa_enabled \
|
||||
and not request.session.get('auth_mfa'):
|
||||
request.session['auth_mfa_required'] = 1
|
||||
if not request.session.get("auth_third_party_done") and request.session.get('auth_backend') in AUTHENTICATION_BACKENDS_THIRD_PARTY:
|
||||
if not request.session.get("auth_third_party_done") and \
|
||||
request.session.get('auth_backend') in AUTHENTICATION_BACKENDS_THIRD_PARTY:
|
||||
request.session['auth_third_party_required'] = 1
|
||||
# 单点登录,超过了自动退出
|
||||
if settings.USER_LOGIN_SINGLE_MACHINE_ENABLED:
|
||||
|
@ -44,43 +36,19 @@ def on_user_auth_login_success(sender, user, request, **kwargs):
|
|||
request.session['auth_session_expiration_required'] = 1
|
||||
|
||||
|
||||
@receiver(openid_user_login_success)
|
||||
def on_oidc_user_login_success(sender, request, user, create=False, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OIDC_CODE
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(openid_user_login_failed)
|
||||
def on_oidc_user_login_failed(sender, username, request, reason, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OIDC_CODE
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
||||
|
||||
@receiver(cas_user_authenticated)
|
||||
def on_cas_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_CAS
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(saml2_user_authenticated)
|
||||
def on_saml2_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_SAML2
|
||||
@receiver(user_auth_success)
|
||||
def on_user_login_success(sender, request, user, backend, create=False, **kwargs):
|
||||
request.session['auth_backend'] = backend
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(saml2_user_authentication_failed)
|
||||
def on_saml2_user_login_failed(sender, request, username, reason, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_SAML2
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
||||
|
||||
@receiver(oauth2_user_login_success)
|
||||
def on_oauth2_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OAUTH2
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(oauth2_user_login_failed)
|
||||
def on_oauth2_user_login_failed(sender, username, request, reason, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OAUTH2
|
||||
@receiver(user_auth_failed)
|
||||
def on_user_login_failed(sender, username, request, reason, backend, **kwargs):
|
||||
request.session['auth_backend'] = backend
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
|
|
@ -3,3 +3,7 @@ from django.dispatch import Signal
|
|||
|
||||
post_auth_success = Signal(providing_args=('user', 'request'))
|
||||
post_auth_failed = Signal(providing_args=('username', 'request', 'reason'))
|
||||
|
||||
|
||||
user_auth_success = Signal(providing_args=('user', 'request', 'backend', 'create'))
|
||||
user_auth_failed = Signal(providing_args=('username', 'request', 'reason', 'backend'))
|
||||
|
|
|
@ -79,6 +79,9 @@ function doRequestAuth() {
|
|||
requestApi({
|
||||
url: url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"X-JMS-LOGIN-TYPE": "W"
|
||||
},
|
||||
success: function (data) {
|
||||
if (!data.error && data.msg === 'ok') {
|
||||
window.onbeforeunload = function(){};
|
||||
|
@ -98,9 +101,6 @@ function doRequestAuth() {
|
|||
},
|
||||
error: function (text, data) {
|
||||
},
|
||||
beforeSend: function(request) {
|
||||
request.setRequestHeader("X-JMS-LOGIN-TYPE", "W");
|
||||
},
|
||||
flash_message: false, // 是否显示flash消息
|
||||
})
|
||||
}
|
||||
|
|
|
@ -330,6 +330,8 @@ class UserLogoutView(TemplateView):
|
|||
return settings.CAS_LOGOUT_URL_NAME
|
||||
elif 'saml2' in backend:
|
||||
return settings.SAML2_LOGOUT_URL_NAME
|
||||
elif 'oauth2' in backend:
|
||||
return settings.AUTH_OAUTH2_LOGOUT_URL_NAME
|
||||
return None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
|
@ -10,5 +10,5 @@ celery_task_pre_key = "CELERY_"
|
|||
KEY_CACHE_RESOURCE_IDS = "RESOURCE_IDS_{}"
|
||||
|
||||
# AD User AccountDisable
|
||||
# https://blog.csdn.net/bytxl/article/details/17763975
|
||||
# https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
|
||||
LDAP_AD_ACCOUNT_DISABLE = 2
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from .device import Device
|
||||
|
||||
|
||||
def open_piico_device(driver_path) -> Device:
|
||||
d = Device()
|
||||
d.open(driver_path)
|
||||
return d
|
|
@ -0,0 +1,59 @@
|
|||
cipher_alg_id = {
|
||||
"sm4_ebc": 0x00000401,
|
||||
"sm4_cbc": 0x00000402,
|
||||
}
|
||||
|
||||
|
||||
class ECCCipher:
|
||||
def __init__(self, session, public_key, private_key):
|
||||
self._session = session
|
||||
self.public_key = public_key
|
||||
self.private_key = private_key
|
||||
|
||||
def encrypt(self, plain_text):
|
||||
return self._session.ecc_encrypt(self.public_key, plain_text, 0x00020800)
|
||||
|
||||
def decrypt(self, cipher_text):
|
||||
return self._session.ecc_decrypt(self.private_key, cipher_text, 0x00020800)
|
||||
|
||||
|
||||
class EBCCipher:
|
||||
|
||||
def __init__(self, session, key_val):
|
||||
self._session = session
|
||||
self._key = self.__get_key(key_val)
|
||||
self._alg = "sm4_ebc"
|
||||
self._iv = None
|
||||
|
||||
def __get_key(self, key_val):
|
||||
key_val = self.__padding(key_val)
|
||||
return self._session.import_key(key_val)
|
||||
|
||||
@staticmethod
|
||||
def __padding(val):
|
||||
# padding
|
||||
val = bytes(val)
|
||||
while len(val) % 16 != 0:
|
||||
val += b'\0'
|
||||
return val
|
||||
|
||||
def encrypt(self, plain_text):
|
||||
plain_text = self.__padding(plain_text)
|
||||
cipher_text = self._session.encrypt(plain_text, self._key, cipher_alg_id[self._alg], self._iv)
|
||||
return bytes(cipher_text)
|
||||
|
||||
def decrypt(self, cipher_text):
|
||||
plain_text = self._session.decrypt(cipher_text, self._key, cipher_alg_id[self._alg], self._iv)
|
||||
return bytes(plain_text)
|
||||
|
||||
def destroy(self):
|
||||
self._session.destroy_cipher_key(self._key)
|
||||
self._session.close()
|
||||
|
||||
|
||||
class CBCCipher(EBCCipher):
|
||||
|
||||
def __init__(self, session, key, iv):
|
||||
super().__init__(session, key)
|
||||
self._iv = iv
|
||||
self._alg = "sm4_cbc"
|
|
@ -0,0 +1,70 @@
|
|||
from ctypes import *
|
||||
|
||||
from .exception import PiicoError
|
||||
from .session import Session
|
||||
from .cipher import *
|
||||
from .digest import *
|
||||
|
||||
|
||||
class Device:
|
||||
_driver = None
|
||||
__device = None
|
||||
|
||||
def open(self, driver_path="./libpiico_ccmu.so"):
|
||||
# load driver
|
||||
self.__load_driver(driver_path)
|
||||
# open device
|
||||
self.__open_device()
|
||||
|
||||
def close(self):
|
||||
if self.__device is None:
|
||||
raise Exception("device not turned on")
|
||||
ret = self._driver.SDF_CloseDevice(self.__device)
|
||||
if ret != 0:
|
||||
raise Exception("turn off device failed")
|
||||
self.__device = None
|
||||
|
||||
def new_session(self):
|
||||
session = c_void_p()
|
||||
ret = self._driver.SDF_OpenSession(self.__device, pointer(session))
|
||||
if ret != 0:
|
||||
raise Exception("create session failed")
|
||||
return Session(self._driver, session)
|
||||
|
||||
def generate_ecc_key_pair(self):
|
||||
session = self.new_session()
|
||||
return session.generate_ecc_key_pair(alg_id=0x00020200)
|
||||
|
||||
def generate_random(self, length=64):
|
||||
session = self.new_session()
|
||||
return session.generate_random(length)
|
||||
|
||||
def new_sm2_ecc_cipher(self, public_key, private_key):
|
||||
session = self.new_session()
|
||||
return ECCCipher(session, public_key, private_key)
|
||||
|
||||
def new_sm4_ebc_cipher(self, key_val):
|
||||
session = self.new_session()
|
||||
return EBCCipher(session, key_val)
|
||||
|
||||
def new_sm4_cbc_cipher(self, key_val, iv):
|
||||
session = self.new_session()
|
||||
return CBCCipher(session, key_val, iv)
|
||||
|
||||
def new_digest(self, mode="sm3"):
|
||||
session = self.new_session()
|
||||
return Digest(session, mode)
|
||||
|
||||
def __load_driver(self, path):
|
||||
# check driver status
|
||||
if self._driver is not None:
|
||||
raise Exception("already load driver")
|
||||
# load driver
|
||||
self._driver = cdll.LoadLibrary(path)
|
||||
|
||||
def __open_device(self):
|
||||
device = c_void_p()
|
||||
ret = self._driver.SDF_OpenDevice(pointer(device))
|
||||
if ret != 0:
|
||||
raise PiicoError("open piico device failed", ret)
|
||||
self.__device = device
|
|
@ -0,0 +1,32 @@
|
|||
hash_alg_id = {
|
||||
"sm3": 0x00000001,
|
||||
"sha1": 0x00000002,
|
||||
"sha256": 0x00000004,
|
||||
"sha512": 0x00000008,
|
||||
}
|
||||
|
||||
|
||||
class Digest:
|
||||
|
||||
def __init__(self, session, alg_name="sm3"):
|
||||
if hash_alg_id[alg_name] is None:
|
||||
raise Exception("unsupported hash alg {}".format(alg_name))
|
||||
|
||||
self._alg_name = alg_name
|
||||
self._session = session
|
||||
self.__init_hash()
|
||||
|
||||
def __init_hash(self):
|
||||
self._session.hash_init(hash_alg_id[self._alg_name])
|
||||
|
||||
def update(self, data):
|
||||
self._session.hash_update(data)
|
||||
|
||||
def final(self):
|
||||
return self._session.hash_final()
|
||||
|
||||
def reset(self):
|
||||
self.__init_hash()
|
||||
|
||||
def destroy(self):
|
||||
self._session.close()
|
|
@ -0,0 +1,71 @@
|
|||
from ctypes import *
|
||||
|
||||
ECCref_MAX_BITS = 512
|
||||
ECCref_MAX_LEN = int((ECCref_MAX_BITS + 7) / 8)
|
||||
|
||||
|
||||
class EncodeMixin:
|
||||
def encode(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ECCrefPublicKey(Structure, EncodeMixin):
|
||||
_fields_ = [
|
||||
('bits', c_uint),
|
||||
('x', c_ubyte * ECCref_MAX_LEN),
|
||||
('y', c_ubyte * ECCref_MAX_LEN),
|
||||
]
|
||||
|
||||
def encode(self):
|
||||
return bytes([0x04]) + bytes(self.x[32:]) + bytes(self.y[32:])
|
||||
|
||||
|
||||
class ECCrefPrivateKey(Structure, EncodeMixin):
|
||||
_fields_ = [
|
||||
('bits', c_uint,),
|
||||
('K', c_ubyte * ECCref_MAX_LEN),
|
||||
]
|
||||
|
||||
def encode(self):
|
||||
return bytes(self.K[32:])
|
||||
|
||||
|
||||
class ECCCipherEncode(EncodeMixin):
|
||||
|
||||
def __init__(self):
|
||||
self.x = None
|
||||
self.y = None
|
||||
self.M = None
|
||||
self.C = None
|
||||
self.L = None
|
||||
|
||||
def encode(self):
|
||||
c1 = bytes(self.x[32:]) + bytes(self.y[32:])
|
||||
c2 = bytes(self.C[:self.L])
|
||||
c3 = bytes(self.M)
|
||||
return bytes([0x04]) + c1 + c2 + c3
|
||||
|
||||
|
||||
def new_ecc_cipher_cla(length):
|
||||
_cache = {}
|
||||
cla_name = "ECCCipher{}".format(length)
|
||||
if _cache.__contains__(cla_name):
|
||||
return _cache[cla_name]
|
||||
else:
|
||||
cla = type(cla_name, (Structure, ECCCipherEncode), {
|
||||
"_fields_": [
|
||||
('x', c_ubyte * ECCref_MAX_LEN),
|
||||
('y', c_ubyte * ECCref_MAX_LEN),
|
||||
('M', c_ubyte * 32),
|
||||
('L', c_uint),
|
||||
('C', c_ubyte * length)
|
||||
]
|
||||
})
|
||||
_cache[cla_name] = cla
|
||||
return cla
|
||||
|
||||
|
||||
class ECCKeyPair:
|
||||
def __init__(self, public_key, private_key):
|
||||
self.public_key = public_key
|
||||
self.private_key = private_key
|
|
@ -0,0 +1,12 @@
|
|||
class PiicoError(Exception):
|
||||
def __init__(self, msg, ret):
|
||||
super().__init__(self)
|
||||
self.__ret = ret
|
||||
self.__msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return "piico error: {} return code: {}".format(self.__msg, self.hex_ret(self.__ret))
|
||||
|
||||
@staticmethod
|
||||
def hex_ret(ret):
|
||||
return hex(ret & ((1 << 32) - 1))
|
|
@ -0,0 +1,36 @@
|
|||
from ctypes import *
|
||||
|
||||
from .ecc import ECCrefPublicKey, ECCrefPrivateKey, ECCKeyPair
|
||||
from .exception import PiicoError
|
||||
from .session_mixin import SM3Mixin, SM4Mixin, SM2Mixin
|
||||
|
||||
|
||||
class Session(SM2Mixin, SM3Mixin, SM4Mixin):
|
||||
def __init__(self, driver, session):
|
||||
super().__init__()
|
||||
self._session = session
|
||||
self._driver = driver
|
||||
|
||||
def get_device_info(self):
|
||||
pass
|
||||
|
||||
def generate_random(self, length=64):
|
||||
random_data = (c_ubyte * length)()
|
||||
ret = self._driver.SDF_GenerateRandom(self._session, c_int(length), random_data)
|
||||
if ret != 0:
|
||||
raise PiicoError("generate random error", ret)
|
||||
return bytes(random_data)
|
||||
|
||||
def generate_ecc_key_pair(self, alg_id):
|
||||
public_key = ECCrefPublicKey()
|
||||
private_key = ECCrefPrivateKey()
|
||||
ret = self._driver.SDF_GenerateKeyPair_ECC(self._session, c_int(alg_id), c_int(256), pointer(public_key),
|
||||
pointer(private_key))
|
||||
if ret != 0:
|
||||
raise PiicoError("generate ecc key pair failed", ret)
|
||||
return ECCKeyPair(public_key.encode(), private_key.encode())
|
||||
|
||||
def close(self):
|
||||
ret = self._driver.SDF_CloseSession(self._session)
|
||||
if ret != 0:
|
||||
raise PiicoError("close session failed", ret)
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
from .ecc import *
|
||||
from .exception import PiicoError
|
||||
|
||||
|
||||
class BaseMixin:
|
||||
|
||||
def __init__(self):
|
||||
self._driver = None
|
||||
self._session = None
|
||||
|
||||
|
||||
class SM2Mixin(BaseMixin):
|
||||
def ecc_encrypt(self, public_key, plain_text, alg_id):
|
||||
|
||||
pos = 1
|
||||
k1 = bytes([0] * 32) + bytes(public_key[pos:pos + 32])
|
||||
k1 = (c_ubyte * len(k1))(*k1)
|
||||
pos += 32
|
||||
k2 = bytes([0] * 32) + bytes(public_key[pos:pos + 32])
|
||||
|
||||
pk = ECCrefPublicKey(c_uint(0x40), (c_ubyte * len(k1))(*k1), (c_ubyte * len(k2))(*k2))
|
||||
|
||||
plain_text = (c_ubyte * len(plain_text))(*plain_text)
|
||||
ecc_data = new_ecc_cipher_cla(len(plain_text))()
|
||||
ret = self._driver.SDF_ExternalEncrypt_ECC(self._session, c_int(alg_id), pointer(pk), plain_text,
|
||||
c_int(len(plain_text)), pointer(ecc_data))
|
||||
if ret != 0:
|
||||
raise Exception("ecc encrypt failed", ret)
|
||||
return ecc_data.encode()
|
||||
|
||||
def ecc_decrypt(self, private_key, cipher_text, alg_id):
|
||||
|
||||
k = bytes([0] * 32) + bytes(private_key[:32])
|
||||
vk = ECCrefPrivateKey(c_uint(0x40), (c_ubyte * len(k))(*k))
|
||||
|
||||
pos = 1
|
||||
# c1
|
||||
x = bytes([0] * 32) + bytes(cipher_text[pos:pos + 32])
|
||||
pos += 32
|
||||
y = bytes([0] * 32) + bytes(cipher_text[pos:pos + 32])
|
||||
pos += 32
|
||||
|
||||
# c2
|
||||
c = bytes(cipher_text[pos:-32])
|
||||
l = len(c)
|
||||
|
||||
# c3
|
||||
m = bytes(cipher_text[-32:])
|
||||
|
||||
ecc_data = new_ecc_cipher_cla(l)(
|
||||
(c_ubyte * 64)(*x),
|
||||
(c_ubyte * 64)(*y),
|
||||
(c_ubyte * 32)(*m),
|
||||
c_uint(l),
|
||||
(c_ubyte * l)(*c),
|
||||
)
|
||||
temp_data = (c_ubyte * l)()
|
||||
temp_data_length = c_int()
|
||||
ret = self._driver.SDF_ExternalDecrypt_ECC(self._session, c_int(alg_id), pointer(vk),
|
||||
pointer(ecc_data),
|
||||
temp_data, pointer(temp_data_length))
|
||||
if ret != 0:
|
||||
raise Exception("ecc decrypt failed", ret)
|
||||
return bytes(temp_data[:temp_data_length.value])
|
||||
|
||||
|
||||
class SM3Mixin(BaseMixin):
|
||||
def hash_init(self, alg_id):
|
||||
ret = self._driver.SDF_HashInit(self._session, c_int(alg_id), None, None, c_int(0))
|
||||
if ret != 0:
|
||||
raise PiicoError("hash init failed,alg id is {}".format(alg_id), ret)
|
||||
|
||||
def hash_update(self, data):
|
||||
data = (c_ubyte * len(data))(*data)
|
||||
ret = self._driver.SDF_HashUpdate(self._session, data, c_int(len(data)))
|
||||
if ret != 0:
|
||||
raise PiicoError("hash update failed", ret)
|
||||
|
||||
def hash_final(self):
|
||||
result_data = (c_ubyte * 32)()
|
||||
result_length = c_int()
|
||||
ret = self._driver.SDF_HashFinal(self._session, result_data, pointer(result_length))
|
||||
if ret != 0:
|
||||
raise PiicoError("hash final failed", ret)
|
||||
return bytes(result_data[:result_length.value])
|
||||
|
||||
|
||||
class SM4Mixin(BaseMixin):
|
||||
|
||||
def import_key(self, key_val):
|
||||
# to c lang
|
||||
key_val = (c_ubyte * len(key_val))(*key_val)
|
||||
|
||||
key = c_void_p()
|
||||
ret = self._driver.SDF_ImportKey(self._session, key_val, c_int(len(key_val)), pointer(key))
|
||||
if ret != 0:
|
||||
raise PiicoError("import key failed", ret)
|
||||
return key
|
||||
|
||||
def destroy_cipher_key(self, key):
|
||||
ret = self._driver.SDF_DestroyKey(self._session, key)
|
||||
if ret != 0:
|
||||
raise Exception("destroy key failed")
|
||||
|
||||
def encrypt(self, plain_text, key, alg, iv=None):
|
||||
return self.__do_cipher_action(plain_text, key, alg, iv, True)
|
||||
|
||||
def decrypt(self, cipher_text, key, alg, iv=None):
|
||||
return self.__do_cipher_action(cipher_text, key, alg, iv, False)
|
||||
|
||||
def __do_cipher_action(self, text, key, alg, iv=None, encrypt=True):
|
||||
text = (c_ubyte * len(text))(*text)
|
||||
if iv is not None:
|
||||
iv = (c_ubyte * len(iv))(*iv)
|
||||
|
||||
temp_data = (c_ubyte * len(text))()
|
||||
temp_data_length = c_int()
|
||||
if encrypt:
|
||||
ret = self._driver.SDF_Encrypt(self._session, key, c_int(alg), iv, text, c_int(len(text)), temp_data,
|
||||
pointer(temp_data_length))
|
||||
if ret != 0:
|
||||
raise PiicoError("encrypt failed", ret)
|
||||
else:
|
||||
ret = self._driver.SDF_Decrypt(self._session, key, c_int(alg), iv, text, c_int(len(text)), temp_data,
|
||||
pointer(temp_data_length))
|
||||
if ret != 0:
|
||||
raise PiicoError("decrypt failed", ret)
|
||||
return temp_data[:temp_data_length.value]
|
|
@ -15,6 +15,7 @@ logger = get_logger(__name__)
|
|||
class BACKENDS(TextChoices):
|
||||
ALIBABA = 'alibaba', _('Alibaba cloud')
|
||||
TENCENT = 'tencent', _('Tencent cloud')
|
||||
HUAWEI = 'huawei', _('Huawei Cloud')
|
||||
CMPP2 = 'cmpp2', _('CMPP v2.0')
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import base64
|
||||
import hashlib
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from common.exceptions import JMSException
|
||||
from common.utils import get_logger
|
||||
|
||||
from .base import BaseSMSClient
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class HuaweiClient:
|
||||
def __init__(self, app_key, app_secret, url, sign_channel_num):
|
||||
self.url = url[:-1] if url.endswith('/') else url
|
||||
self.app_key = app_key
|
||||
self.app_secret = app_secret
|
||||
self.sign_channel_num = sign_channel_num
|
||||
|
||||
def build_wsse_header(self):
|
||||
now = time.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
nonce = str(uuid.uuid4()).replace('-', '')
|
||||
digest = hashlib.sha256((nonce + now + self.app_secret).encode()).hexdigest()
|
||||
digestBase64 = base64.b64encode(digest.encode()).decode()
|
||||
formatter = 'UsernameToken Username="{}",PasswordDigest="{}",Nonce="{}",Created="{}"'
|
||||
return formatter.format(self.app_key, digestBase64, nonce, now)
|
||||
|
||||
def send_sms(self, receiver, signature, template_id, template_param):
|
||||
sms_url = '%s/%s' % (self.url, 'sms/batchSendSms/v1')
|
||||
headers = {
|
||||
'Authorization': 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"',
|
||||
'X-WSSE': self.build_wsse_header()
|
||||
}
|
||||
body = {
|
||||
'from': self.sign_channel_num, 'to': receiver, 'templateId': template_id,
|
||||
'templateParas': template_param, 'signature': signature
|
||||
}
|
||||
try:
|
||||
response = requests.post(sms_url, headers=headers, data=body)
|
||||
msg = response.json()
|
||||
except Exception as error:
|
||||
raise JMSException(code='response_bad', detail=error)
|
||||
return msg
|
||||
|
||||
|
||||
class HuaweiSMS(BaseSMSClient):
|
||||
SIGN_AND_TMPL_SETTING_FIELD_PREFIX = 'HUAWEI'
|
||||
|
||||
@classmethod
|
||||
def new_from_settings(cls):
|
||||
return cls(
|
||||
app_key=settings.HUAWEI_APP_KEY,
|
||||
app_secret=settings.HUAWEI_APP_SECRET,
|
||||
url=settings.HUAWEI_SMS_ENDPOINT,
|
||||
sign_channel_num=settings.HUAWEI_SIGN_CHANNEL_NUM
|
||||
)
|
||||
|
||||
def __init__(self, app_key: str, app_secret: str, url: str, sign_channel_num: str):
|
||||
self.client = HuaweiClient(app_key, app_secret, url, sign_channel_num)
|
||||
|
||||
def send_sms(
|
||||
self, phone_numbers: list, sign_name: str, template_code: str,
|
||||
template_param: OrderedDict, **kwargs
|
||||
):
|
||||
phone_numbers_str = ','.join(phone_numbers)
|
||||
template_param = '["%s"]' % template_param.get('code')
|
||||
req_params = {
|
||||
'receiver': phone_numbers_str, 'signature': sign_name,
|
||||
'template_id': template_code, 'template_param': template_param
|
||||
}
|
||||
try:
|
||||
logger.info(f'Huawei sms send: '
|
||||
f'phone_numbers={phone_numbers} '
|
||||
f'sign_name={sign_name} '
|
||||
f'template_code={template_code} '
|
||||
f'template_param={template_param}')
|
||||
|
||||
resp_msg = self.client.send_sms(**req_params)
|
||||
|
||||
except Exception as error:
|
||||
raise JMSException(code='response_bad', detail=error)
|
||||
|
||||
if resp_msg.get('code' != '000000'):
|
||||
raise JMSException(code='response_bad', detail=resp_msg)
|
||||
return resp_msg
|
||||
|
||||
|
||||
client = HuaweiSMS
|
|
@ -1,15 +1,18 @@
|
|||
import base64
|
||||
import logging
|
||||
import re
|
||||
|
||||
from Cryptodome.Cipher import AES, PKCS1_v1_5
|
||||
from Cryptodome.Random import get_random_bytes
|
||||
from Cryptodome.PublicKey import RSA
|
||||
from Cryptodome.Util.Padding import pad
|
||||
from Cryptodome import Random
|
||||
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from common.sdk.gm import piico
|
||||
|
||||
secret_pattern = re.compile(r'password|secret|key|token', re.IGNORECASE)
|
||||
|
||||
|
@ -63,6 +66,25 @@ class GMSM4EcbCrypto(BaseCrypto):
|
|||
return self.sm4_decryptor.crypt_ecb(data)
|
||||
|
||||
|
||||
class PiicoSM4EcbCrypto(BaseCrypto):
|
||||
|
||||
@staticmethod
|
||||
def to_16(key):
|
||||
while len(key) % 16 != 0:
|
||||
key += b'\0'
|
||||
return key # 返回bytes
|
||||
|
||||
def __init__(self, key, device: piico.Device):
|
||||
key = padding_key(key, 16)
|
||||
self.cipher = device.new_sm4_ebc_cipher(key)
|
||||
|
||||
def _encrypt(self, data: bytes) -> bytes:
|
||||
return self.cipher.encrypt(self.to_16(data))
|
||||
|
||||
def _decrypt(self, data: bytes) -> bytes:
|
||||
return self.cipher.decrypt(data)
|
||||
|
||||
|
||||
class AESCrypto:
|
||||
"""
|
||||
AES
|
||||
|
@ -107,7 +129,15 @@ class AESCryptoGCM:
|
|||
"""
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = padding_key(key)
|
||||
self.key = self.process_key(key)
|
||||
|
||||
@staticmethod
|
||||
def process_key(key):
|
||||
if not isinstance(key, bytes):
|
||||
key = bytes(key, encoding='utf-8')
|
||||
if len(key) >= 32:
|
||||
return key[:32]
|
||||
return pad(key, 32)
|
||||
|
||||
def encrypt(self, text):
|
||||
"""
|
||||
|
@ -155,6 +185,11 @@ def get_gm_sm4_ecb_crypto(key=None):
|
|||
return GMSM4EcbCrypto(key)
|
||||
|
||||
|
||||
def get_piico_gm_sm4_ecb_crypto(device, key=None):
|
||||
key = key or settings.SECRET_KEY
|
||||
return PiicoSM4EcbCrypto(key, device)
|
||||
|
||||
|
||||
aes_ecb_crypto = get_aes_crypto(mode='ECB')
|
||||
aes_crypto = get_aes_crypto(mode='GCM')
|
||||
gm_sm4_ecb_crypto = get_gm_sm4_ecb_crypto()
|
||||
|
@ -174,10 +209,16 @@ class Crypto:
|
|||
crypt_algo = settings.SECURITY_DATA_CRYPTO_ALGO
|
||||
if not crypt_algo:
|
||||
if settings.GMSSL_ENABLED:
|
||||
crypt_algo = 'gm'
|
||||
if settings.PIICO_DEVICE_ENABLE:
|
||||
piico_driver_path = settings.PIICO_DRIVER_PATH if settings.PIICO_DRIVER_PATH \
|
||||
else "./lib/libpiico_ccmu.so"
|
||||
device = piico.open_piico_device(piico_driver_path)
|
||||
self.cryptor_map["piico_gm"] = get_piico_gm_sm4_ecb_crypto(device)
|
||||
crypt_algo = 'piico_gm'
|
||||
else:
|
||||
crypt_algo = 'gm'
|
||||
else:
|
||||
crypt_algo = 'aes'
|
||||
|
||||
cryptor = self.cryptor_map.get(crypt_algo, None)
|
||||
if cryptor is None:
|
||||
raise ImproperlyConfigured(
|
||||
|
|
|
@ -224,6 +224,9 @@ class Config(dict):
|
|||
'CONNECTION_TOKEN_EXPIRATION': 5 * 60,
|
||||
|
||||
# Custom Config
|
||||
'AUTH_CUSTOM': False,
|
||||
'AUTH_CUSTOM_FILE_MD5': '',
|
||||
|
||||
# Auth LDAP settings
|
||||
'AUTH_LDAP': False,
|
||||
'AUTH_LDAP_SERVER_URI': 'ldap://localhost:389',
|
||||
|
@ -331,8 +334,10 @@ class Config(dict):
|
|||
'AUTH_OAUTH2_CLIENT_ID': 'client-id',
|
||||
'AUTH_OAUTH2_SCOPE': '',
|
||||
'AUTH_OAUTH2_CLIENT_SECRET': '',
|
||||
'AUTH_OAUTH2_LOGOUT_COMPLETELY': True,
|
||||
'AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT': 'https://oauth2.example.com/authorize',
|
||||
'AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT': 'https://oauth2.example.com/userinfo',
|
||||
'AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT': 'https://oauth2.example.com/logout',
|
||||
'AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT': 'https://oauth2.example.com/access_token',
|
||||
'AUTH_OAUTH2_ACCESS_TOKEN_METHOD': 'GET',
|
||||
'AUTH_OAUTH2_USER_ATTR_MAP': {
|
||||
|
@ -376,6 +381,13 @@ class Config(dict):
|
|||
'TENCENT_VERIFY_SIGN_NAME': '',
|
||||
'TENCENT_VERIFY_TEMPLATE_CODE': '',
|
||||
|
||||
'HUAWEI_APP_KEY': '',
|
||||
'HUAWEI_APP_SECRET': '',
|
||||
'HUAWEI_SMS_ENDPOINT': '',
|
||||
'HUAWEI_SIGN_CHANNEL_NUM': '',
|
||||
'HUAWEI_VERIFY_SIGN_NAME': '',
|
||||
'HUAWEI_VERIFY_TEMPLATE_CODE': '',
|
||||
|
||||
'CMPP2_HOST': '',
|
||||
'CMPP2_PORT': 7890,
|
||||
'CMPP2_SP_ID': '',
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#
|
||||
import os
|
||||
import ldap
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..const import CONFIG, PROJECT_DIR, BASE_DIR
|
||||
|
||||
|
@ -165,6 +164,7 @@ AUTH_OAUTH2_USER_ATTR_MAP = CONFIG.AUTH_OAUTH2_USER_ATTR_MAP
|
|||
AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:oauth2:login-callback'
|
||||
AUTH_OAUTH2_AUTHENTICATION_REDIRECT_URI = '/'
|
||||
AUTH_OAUTH2_AUTHENTICATION_FAILURE_REDIRECT_URI = '/'
|
||||
AUTH_OAUTH2_LOGOUT_URL_NAME = "authentication:oauth2:logout"
|
||||
|
||||
# 临时 token
|
||||
AUTH_TEMP_TOKEN = CONFIG.AUTH_TEMP_TOKEN
|
||||
|
@ -195,7 +195,7 @@ AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthent
|
|||
AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
|
||||
AUTH_BACKEND_OAUTH2 = 'authentication.backends.oauth2.OAuth2Backend'
|
||||
AUTH_BACKEND_TEMP_TOKEN = 'authentication.backends.token.TempTokenAuthBackend'
|
||||
|
||||
AUTH_BACKEND_CUSTOM = 'authentication.backends.custom.CustomAuthBackend'
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# 只做权限校验
|
||||
|
@ -208,9 +208,32 @@ AUTHENTICATION_BACKENDS = [
|
|||
# 扫码模式
|
||||
AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU,
|
||||
# Token模式
|
||||
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN
|
||||
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN,
|
||||
]
|
||||
|
||||
|
||||
def get_file_md5(filepath):
|
||||
import hashlib
|
||||
# 创建md5对象
|
||||
m = hashlib.md5()
|
||||
with open(filepath, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(4096)
|
||||
if not data:
|
||||
break
|
||||
# 更新md5对象
|
||||
m.update(data)
|
||||
# 返回md5对象
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
AUTH_CUSTOM = CONFIG.AUTH_CUSTOM
|
||||
AUTH_CUSTOM_FILE_MD5 = CONFIG.AUTH_CUSTOM_FILE_MD5
|
||||
AUTH_CUSTOM_FILE_PATH = os.path.join(PROJECT_DIR, 'data', 'auth', 'main.py')
|
||||
if AUTH_CUSTOM and AUTH_CUSTOM_FILE_MD5 == get_file_md5(AUTH_CUSTOM_FILE_PATH):
|
||||
# 自定义认证模块
|
||||
AUTHENTICATION_BACKENDS.append(AUTH_BACKEND_CUSTOM)
|
||||
|
||||
AUTHENTICATION_BACKENDS_THIRD_PARTY = [AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_CAS, AUTH_BACKEND_SAML2, AUTH_BACKEND_OAUTH2]
|
||||
ONLY_ALLOW_EXIST_USER_AUTH = CONFIG.ONLY_ALLOW_EXIST_USER_AUTH
|
||||
ONLY_ALLOW_AUTH_FROM_SOURCE = CONFIG.ONLY_ALLOW_AUTH_FROM_SOURCE
|
||||
|
|
|
@ -142,6 +142,7 @@ WSGI_APPLICATION = 'jumpserver.wsgi.application'
|
|||
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('index')
|
||||
LOGIN_URL = reverse_lazy('authentication:login')
|
||||
LOGOUT_REDIRECT_URL = CONFIG.LOGOUT_REDIRECT_URL
|
||||
|
||||
SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN
|
||||
CSRF_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN
|
||||
|
|
|
@ -111,6 +111,8 @@ HTTP_LISTEN_PORT = CONFIG.HTTP_LISTEN_PORT
|
|||
WS_LISTEN_PORT = CONFIG.WS_LISTEN_PORT
|
||||
LOGIN_LOG_KEEP_DAYS = CONFIG.LOGIN_LOG_KEEP_DAYS
|
||||
TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS
|
||||
OPERATE_LOG_KEEP_DAYS = CONFIG.OPERATE_LOG_KEEP_DAYS
|
||||
FTP_LOG_KEEP_DAYS = CONFIG.FTP_LOG_KEEP_DAYS
|
||||
ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL
|
||||
WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
|||
'sms': serializers.SMSSettingSerializer,
|
||||
'alibaba': serializers.AlibabaSMSSettingSerializer,
|
||||
'tencent': serializers.TencentSMSSettingSerializer,
|
||||
'huawei': serializers.HuaweiSMSSettingSerializer,
|
||||
'cmpp2': serializers.CMPP2SMSSettingSerializer,
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class SMSTestingAPI(GenericAPIView):
|
|||
backends_serializer = {
|
||||
'alibaba': serializers.AlibabaSMSSettingSerializer,
|
||||
'tencent': serializers.TencentSMSSettingSerializer,
|
||||
'huawei': serializers.HuaweiSMSSettingSerializer,
|
||||
'cmpp2': serializers.CMPP2SMSSettingSerializer
|
||||
}
|
||||
rbac_perms = {
|
||||
|
@ -82,6 +83,22 @@ class SMSTestingAPI(GenericAPIView):
|
|||
}
|
||||
return init_params, send_sms_params
|
||||
|
||||
def get_huawei_params(self, data):
|
||||
init_params = {
|
||||
'app_key': data['HUAWEI_APP_KEY'],
|
||||
'app_secret': self.get_or_from_setting(
|
||||
'HUAWEI_APP_SECRET', data.get('HUAWEI_APP_SECRET')
|
||||
),
|
||||
'url': data['HUAWEI_SMS_ENDPOINT'],
|
||||
'sign_channel_num': data['HUAWEI_SIGN_CHANNEL_NUM'],
|
||||
}
|
||||
send_sms_params = {
|
||||
'sign_name': data['HUAWEI_VERIFY_SIGN_NAME'],
|
||||
'template_code': data['HUAWEI_VERIFY_TEMPLATE_CODE'],
|
||||
'template_param': OrderedDict(code='666666')
|
||||
}
|
||||
return init_params, send_sms_params
|
||||
|
||||
def get_cmpp2_params(self, data):
|
||||
init_params = {
|
||||
'host': data['CMPP2_HOST'], 'port': data['CMPP2_PORT'],
|
||||
|
|
|
@ -47,6 +47,10 @@ class OAuth2SettingSerializer(serializers.Serializer):
|
|||
AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = serializers.CharField(
|
||||
required=True, max_length=1024, label=_('Provider userinfo endpoint')
|
||||
)
|
||||
AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT = serializers.CharField(
|
||||
required=False, max_length=1024, label=_('Provider end session endpoint')
|
||||
)
|
||||
AUTH_OAUTH2_LOGOUT_COMPLETELY = serializers.BooleanField(required=False, label=_('Logout completely'))
|
||||
AUTH_OAUTH2_USER_ATTR_MAP = serializers.DictField(
|
||||
required=True, label=_('User attr map')
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ from common.sdk.sms import BACKENDS
|
|||
|
||||
__all__ = [
|
||||
'SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer',
|
||||
'CMPP2SMSSettingSerializer'
|
||||
'HuaweiSMSSettingSerializer', 'CMPP2SMSSettingSerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -52,6 +52,15 @@ class TencentSMSSettingSerializer(BaseSMSSettingSerializer):
|
|||
TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
|
||||
|
||||
|
||||
class HuaweiSMSSettingSerializer(BaseSMSSettingSerializer):
|
||||
HUAWEI_APP_KEY = serializers.CharField(max_length=256, required=True, label='App key')
|
||||
HUAWEI_APP_SECRET = EncryptedField(max_length=256, required=False, label='App secret')
|
||||
HUAWEI_SMS_ENDPOINT = serializers.CharField(max_length=1024, required=True, label=_('App Access Address'))
|
||||
HUAWEI_SIGN_CHANNEL_NUM = serializers.CharField(max_length=1024, required=True, label=_('Signature channel number'))
|
||||
HUAWEI_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
|
||||
HUAWEI_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
|
||||
|
||||
|
||||
class CMPP2SMSSettingSerializer(BaseSMSSettingSerializer):
|
||||
CMPP2_HOST = serializers.CharField(max_length=256, required=True, label=_('Host'))
|
||||
CMPP2_PORT = serializers.IntegerField(default=7890, label=_('Port'))
|
||||
|
|
|
@ -13,7 +13,7 @@ def telnet(dest_addr, port_number=23, timeout=10):
|
|||
return False, str(e)
|
||||
expected_regexes = [bytes(PROMPT_REGEX, encoding='ascii')]
|
||||
index, prompt_regex, output = connection.expect(expected_regexes, timeout=3)
|
||||
return True, output.decode('ascii')
|
||||
return True, output.decode('utf-8', 'ignore')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -270,13 +270,13 @@ function requestApi(props) {
|
|||
if (typeof(dataBody) === "object") {
|
||||
dataBody = JSON.stringify(dataBody)
|
||||
}
|
||||
var beforeSend = props.beforeSend || function (request) {}
|
||||
var headers = props.headers || {}
|
||||
|
||||
$.ajax({
|
||||
url: props.url,
|
||||
type: props.method || "PATCH",
|
||||
headers: headers,
|
||||
data: dataBody,
|
||||
beforeSend: beforeSend,
|
||||
contentType: props.content_type || "application/json; charset=utf-8",
|
||||
dataType: props.data_type || "json"
|
||||
}).done(function (data, textStatue, jqXHR) {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.13 on 2022-08-24 02:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0039_auto_20211229_1852'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source'),
|
||||
),
|
||||
]
|
|
@ -629,6 +629,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
cas = 'cas', 'CAS'
|
||||
saml2 = 'saml2', 'SAML2'
|
||||
oauth2 = 'oauth2', 'OAuth2'
|
||||
custom = 'custom', 'Custom'
|
||||
|
||||
SOURCE_BACKEND_MAPPING = {
|
||||
Source.local: [
|
||||
|
@ -656,6 +657,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
Source.oauth2: [
|
||||
settings.AUTH_BACKEND_OAUTH2
|
||||
],
|
||||
Source.custom: [
|
||||
settings.AUTH_BACKEND_CUSTOM
|
||||
]
|
||||
}
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
apt install \
|
||||
g++ make iputils-ping default-libmysqlclient-dev libpq-dev \
|
||||
libffi-dev libldap2-dev libsasl2-dev sshpass pkg-config libxml2-dev \
|
||||
libffi-dev libldap2-dev libsasl2-dev openssh-client sshpass pkg-config libxml2-dev \
|
||||
libxmlsec1-dev libxmlsec1-openssl libaio-dev freetds-dev
|
||||
|
|
|
@ -134,9 +134,10 @@ django-mysql==3.9.0
|
|||
django-redis==5.2.0
|
||||
python-redis-lock==3.7.0
|
||||
redis==4.3.3
|
||||
pymongo==4.2.0
|
||||
# Debug
|
||||
ipython==8.4.0
|
||||
ForgeryPy3==0.3.1
|
||||
django-debug-toolbar==3.5
|
||||
Pympler==1.0.1
|
||||
IPy==1.1
|
||||
IPy==1.1
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
yum -y install \
|
||||
gcc-c++ sshpass mariadb-devel openldap-devel libxml2-devel \
|
||||
gcc-c++ sshpass mariadb-devel openldap-devel openssh-clients libxml2-devel \
|
||||
xmlsec1-devel xmlsec1-openssl-devel libtool-ltdl-devel \
|
||||
postgresql-devel
|
||||
|
|
Loading…
Reference in New Issue