From 24a1738e731b604dc79b1eb281a1ac934869121b Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 19 Aug 2022 10:34:16 +0800 Subject: [PATCH 01/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AB=AF=E6=B8=B2=E6=9F=93=E8=AF=B7=E6=B1=82=E7=BC=BA?= =?UTF-8?q?=E5=B0=91csrf=20token=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/authentication/login_wait_confirm.html | 3 --- apps/static/js/jumpserver.js | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index a74c8db26..6107b3bc8 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -98,9 +98,6 @@ function doRequestAuth() { }, error: function (text, data) { }, - beforeSend: function(request) { - request.setRequestHeader("X-JMS-LOGIN-TYPE", "W"); - }, flash_message: false, // 是否显示flash消息 }) } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index e3537cb84..974652bb1 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -132,6 +132,7 @@ function setAjaxCSRFToken() { $.ajaxSetup({ beforeSend: function (xhr, settings) { + xhr.setRequestHeader("X-JMS-LOGIN-TYPE", "W"); if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } @@ -270,13 +271,11 @@ function requestApi(props) { if (typeof(dataBody) === "object") { dataBody = JSON.stringify(dataBody) } - var beforeSend = props.beforeSend || function (request) {} $.ajax({ url: props.url, type: props.method || "PATCH", data: dataBody, - beforeSend: beforeSend, contentType: props.content_type || "application/json; charset=utf-8", dataType: props.data_type || "json" }).done(function (data, textStatue, jqXHR) { From 8ae98887ee072ec567394db0fbf64087a70e8ba4 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 19 Aug 2022 14:19:47 +0800 Subject: [PATCH 02/26] =?UTF-8?q?Revert=20"fix:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AB=AF=E6=B8=B2=E6=9F=93=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=BC=BA=E5=B0=91csrf=20token=20=E9=97=AE=E9=A2=98"=20(#8780)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 24a1738e731b604dc79b1eb281a1ac934869121b. --- .../templates/authentication/login_wait_confirm.html | 3 +++ apps/static/js/jumpserver.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index 6107b3bc8..a74c8db26 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -98,6 +98,9 @@ function doRequestAuth() { }, error: function (text, data) { }, + beforeSend: function(request) { + request.setRequestHeader("X-JMS-LOGIN-TYPE", "W"); + }, flash_message: false, // 是否显示flash消息 }) } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 974652bb1..e3537cb84 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -132,7 +132,6 @@ function setAjaxCSRFToken() { $.ajaxSetup({ beforeSend: function (xhr, settings) { - xhr.setRequestHeader("X-JMS-LOGIN-TYPE", "W"); if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } @@ -271,11 +270,13 @@ function requestApi(props) { if (typeof(dataBody) === "object") { dataBody = JSON.stringify(dataBody) } + var beforeSend = props.beforeSend || function (request) {} $.ajax({ url: props.url, type: props.method || "PATCH", data: dataBody, + beforeSend: beforeSend, contentType: props.content_type || "application/json; charset=utf-8", dataType: props.data_type || "json" }).done(function (data, textStatue, jqXHR) { From 1432fe16095ca4b604e72189b6f6184869733ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Fri, 19 Aug 2022 13:59:48 +0800 Subject: [PATCH 03/26] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=20openssh-clien?= =?UTF-8?q?t=20=E4=BE=9D=E8=B5=96=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/deb_pkg.sh | 2 +- requirements/rpm_pkg.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/deb_pkg.sh b/requirements/deb_pkg.sh index 3643d25d0..18d0b8f06 100644 --- a/requirements/deb_pkg.sh +++ b/requirements/deb_pkg.sh @@ -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 diff --git a/requirements/rpm_pkg.sh b/requirements/rpm_pkg.sh index b926690dc..53df8bf0d 100644 --- a/requirements/rpm_pkg.sh +++ b/requirements/rpm_pkg.sh @@ -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 From 37b150bc04d350db0725f19220574bc82570cbdf Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 19 Aug 2022 14:38:59 +0800 Subject: [PATCH 04/26] =?UTF-8?q?fix:=20=E8=A1=A8=E5=8D=95=E6=8F=90?= =?UTF-8?q?=E4=BA=A4csrftoken=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/authentication/login_wait_confirm.html | 6 +++--- apps/static/js/jumpserver.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index a74c8db26..157ee1d1a 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -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消息 }) } diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index e3537cb84..c95c06c2c 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -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) { From 5f1b7ff8f950b6c11c023273bb3c52f29727a6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Sat, 20 Aug 2022 19:15:51 +0800 Subject: [PATCH 05/26] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index e2ec06457..7e6eac422 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ ARG DEPENDENCIES=" \ libxmlsec1-dev \ libxmlsec1-openssl \ libaio-dev \ + openssh-client \ sshpass" ARG TOOLS=" \ From 60cb1f8136ac13dffa908e6a39a327557cccc402 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Aug 2022 14:13:26 +0800 Subject: [PATCH 06/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=20gcm=20key=20padding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/crypto.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index 5943ad9e3..0a75ee4e3 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -4,6 +4,7 @@ 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 @@ -107,7 +108,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): """ From 8772cd8c71fc36619d79af91c01ef5f659dbc82e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 23 Aug 2022 17:40:01 +0800 Subject: [PATCH 07/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20piico=20?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=9B=BD=E5=AF=86=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/sdk/gm/__init__.py | 0 apps/common/sdk/gm/piico/__init__.py | 7 ++ apps/common/sdk/gm/piico/cipher.py | 59 ++++++++++ apps/common/sdk/gm/piico/device.py | 70 ++++++++++++ apps/common/sdk/gm/piico/digest.py | 32 ++++++ apps/common/sdk/gm/piico/ecc.py | 71 ++++++++++++ apps/common/sdk/gm/piico/exception.py | 12 ++ apps/common/sdk/gm/piico/session.py | 36 ++++++ apps/common/sdk/gm/piico/session_mixin.py | 129 ++++++++++++++++++++++ apps/common/sdk/gm/piico/sign.py | 0 apps/common/utils/crypto.py | 36 +++++- 11 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 apps/common/sdk/gm/__init__.py create mode 100644 apps/common/sdk/gm/piico/__init__.py create mode 100644 apps/common/sdk/gm/piico/cipher.py create mode 100644 apps/common/sdk/gm/piico/device.py create mode 100644 apps/common/sdk/gm/piico/digest.py create mode 100644 apps/common/sdk/gm/piico/ecc.py create mode 100644 apps/common/sdk/gm/piico/exception.py create mode 100644 apps/common/sdk/gm/piico/session.py create mode 100644 apps/common/sdk/gm/piico/session_mixin.py create mode 100644 apps/common/sdk/gm/piico/sign.py diff --git a/apps/common/sdk/gm/__init__.py b/apps/common/sdk/gm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/sdk/gm/piico/__init__.py b/apps/common/sdk/gm/piico/__init__.py new file mode 100644 index 000000000..936a89ca6 --- /dev/null +++ b/apps/common/sdk/gm/piico/__init__.py @@ -0,0 +1,7 @@ +from .device import Device + + +def open_piico_device(driver_path) -> Device: + d = Device() + d.open(driver_path) + return d diff --git a/apps/common/sdk/gm/piico/cipher.py b/apps/common/sdk/gm/piico/cipher.py new file mode 100644 index 000000000..7f3a0b7b0 --- /dev/null +++ b/apps/common/sdk/gm/piico/cipher.py @@ -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" diff --git a/apps/common/sdk/gm/piico/device.py b/apps/common/sdk/gm/piico/device.py new file mode 100644 index 000000000..0d93f9e45 --- /dev/null +++ b/apps/common/sdk/gm/piico/device.py @@ -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"): + # 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 not 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 not 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 not ret == 0: + raise PiicoError("open piico device failed", ret) + self.__device = device diff --git a/apps/common/sdk/gm/piico/digest.py b/apps/common/sdk/gm/piico/digest.py new file mode 100644 index 000000000..2156b6bc0 --- /dev/null +++ b/apps/common/sdk/gm/piico/digest.py @@ -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() diff --git a/apps/common/sdk/gm/piico/ecc.py b/apps/common/sdk/gm/piico/ecc.py new file mode 100644 index 000000000..db84b8219 --- /dev/null +++ b/apps/common/sdk/gm/piico/ecc.py @@ -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 diff --git a/apps/common/sdk/gm/piico/exception.py b/apps/common/sdk/gm/piico/exception.py new file mode 100644 index 000000000..67180fa4d --- /dev/null +++ b/apps/common/sdk/gm/piico/exception.py @@ -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)) diff --git a/apps/common/sdk/gm/piico/session.py b/apps/common/sdk/gm/piico/session.py new file mode 100644 index 000000000..63c5204b3 --- /dev/null +++ b/apps/common/sdk/gm/piico/session.py @@ -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 not 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 not 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 not ret == 0: + raise PiicoError("close session failed", ret) diff --git a/apps/common/sdk/gm/piico/session_mixin.py b/apps/common/sdk/gm/piico/session_mixin.py new file mode 100644 index 000000000..ebefca8ac --- /dev/null +++ b/apps/common/sdk/gm/piico/session_mixin.py @@ -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 not 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 not 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 not 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 not 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 not 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 not 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 not 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 not 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 not ret == 0: + raise PiicoError("decrypt failed", ret) + return temp_data[:temp_data_length.value] diff --git a/apps/common/sdk/gm/piico/sign.py b/apps/common/sdk/gm/piico/sign.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index 0a75ee4e3..da921b947 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -1,6 +1,7 @@ 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 @@ -11,6 +12,7 @@ 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) @@ -64,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 @@ -164,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() @@ -183,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( From 51820f23bf46130f35dd64303687099f29adc674 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 23 Aug 2022 20:19:53 +0800 Subject: [PATCH 08/26] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=A1=A8=E8=BE=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/sdk/gm/piico/device.py | 8 ++++---- apps/common/sdk/gm/piico/session.py | 6 +++--- apps/common/sdk/gm/piico/session_mixin.py | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/common/sdk/gm/piico/device.py b/apps/common/sdk/gm/piico/device.py index 0d93f9e45..a5644bfee 100644 --- a/apps/common/sdk/gm/piico/device.py +++ b/apps/common/sdk/gm/piico/device.py @@ -10,7 +10,7 @@ class Device: _driver = None __device = None - def open(self, driver_path="./libpiico_ccmu"): + def open(self, driver_path="./libpiico_ccmu.so"): # load driver self.__load_driver(driver_path) # open device @@ -20,14 +20,14 @@ class Device: if self.__device is None: raise Exception("device not turned on") ret = self._driver.SDF_CloseDevice(self.__device) - if not ret == 0: + 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 not ret == 0: + if ret != 0: raise Exception("create session failed") return Session(self._driver, session) @@ -65,6 +65,6 @@ class Device: def __open_device(self): device = c_void_p() ret = self._driver.SDF_OpenDevice(pointer(device)) - if not ret == 0: + if ret != 0: raise PiicoError("open piico device failed", ret) self.__device = device diff --git a/apps/common/sdk/gm/piico/session.py b/apps/common/sdk/gm/piico/session.py index 63c5204b3..84b748f78 100644 --- a/apps/common/sdk/gm/piico/session.py +++ b/apps/common/sdk/gm/piico/session.py @@ -17,7 +17,7 @@ class Session(SM2Mixin, SM3Mixin, SM4Mixin): def generate_random(self, length=64): random_data = (c_ubyte * length)() ret = self._driver.SDF_GenerateRandom(self._session, c_int(length), random_data) - if not ret == 0: + if ret != 0: raise PiicoError("generate random error", ret) return bytes(random_data) @@ -26,11 +26,11 @@ class Session(SM2Mixin, SM3Mixin, SM4Mixin): 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 not ret == 0: + 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 not ret == 0: + if ret != 0: raise PiicoError("close session failed", ret) diff --git a/apps/common/sdk/gm/piico/session_mixin.py b/apps/common/sdk/gm/piico/session_mixin.py index ebefca8ac..d625f627f 100644 --- a/apps/common/sdk/gm/piico/session_mixin.py +++ b/apps/common/sdk/gm/piico/session_mixin.py @@ -25,7 +25,7 @@ class SM2Mixin(BaseMixin): 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 not ret == 0: + if ret != 0: raise Exception("ecc encrypt failed", ret) return ecc_data.encode() @@ -60,7 +60,7 @@ class SM2Mixin(BaseMixin): ret = self._driver.SDF_ExternalDecrypt_ECC(self._session, c_int(alg_id), pointer(vk), pointer(ecc_data), temp_data, pointer(temp_data_length)) - if not ret == 0: + if ret != 0: raise Exception("ecc decrypt failed", ret) return bytes(temp_data[:temp_data_length.value]) @@ -68,20 +68,20 @@ class SM2Mixin(BaseMixin): 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 not ret == 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 not ret == 0: + 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 not ret == 0: + if ret != 0: raise PiicoError("hash final failed", ret) return bytes(result_data[:result_length.value]) @@ -94,13 +94,13 @@ class SM4Mixin(BaseMixin): key = c_void_p() ret = self._driver.SDF_ImportKey(self._session, key_val, c_int(len(key_val)), pointer(key)) - if not ret == 0: + 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 not ret == 0: + if ret != 0: raise Exception("destroy key failed") def encrypt(self, plain_text, key, alg, iv=None): @@ -119,11 +119,11 @@ class SM4Mixin(BaseMixin): 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 not ret == 0: + 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 not ret == 0: + if ret != 0: raise PiicoError("decrypt failed", ret) return temp_data[:temp_data_length.value] From 7151201d58c489f171ba5e281d4834c1d46f64ef Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 24 Aug 2022 11:41:48 +0800 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=AE=A4=E8=AF=81=20backend=EF=BC=9B?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=85=B6=E4=BB=96=E8=AE=A4=E8=AF=81=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E4=BF=A1=E5=8F=B7=E8=A7=A6=E5=8F=91=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/custom.py | 64 +++++++++++++++++++ .../backends/oauth2/backends.py | 14 ++-- .../authentication/backends/oauth2/signals.py | 2 - apps/authentication/backends/oidc/backends.py | 34 ++++++---- apps/authentication/backends/oidc/signals.py | 2 - .../authentication/backends/saml2/backends.py | 14 ++-- apps/authentication/backends/saml2/signals.py | 2 - apps/authentication/signal_handlers.py | 50 +++------------ apps/authentication/signals.py | 4 ++ apps/jumpserver/settings/auth.py | 5 +- .../migrations/0040_alter_user_source.py | 18 ++++++ apps/users/models/user.py | 4 ++ 12 files changed, 142 insertions(+), 71 deletions(-) create mode 100644 apps/authentication/backends/custom.py create mode 100644 apps/users/migrations/0040_alter_user_source.py diff --git a/apps/authentication/backends/custom.py b/apps/authentication/backends/custom.py new file mode 100644 index 000000000..4432e2919 --- /dev/null +++ b/apps/authentication/backends/custom.py @@ -0,0 +1,64 @@ +import os +from django.conf import settings +import inspect +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__) + + +class CustomAuthBackend(JMSModelBackend): + custom_auth_method_path = 'data.auth.main.authenticate' + + def is_enabled(self): + try: + self.load_authenticate_method() + except Exception as e: + logger.warning('Not enabled custom auth backend: {}'.format(e)) + return False + else: + logger.info('Enabled custom auth backend') + return True + + @staticmethod + def get_or_create_user_from_userinfo(userinfo: dict): + username = userinfo['username'] + attrs = ['name', 'username', 'email'] + 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 load_authenticate_method(self): + return import_string(self.custom_auth_method_path) + + def authenticate(self, request, username=None, password=None, **kwargs): + try: + authenticate = self.load_authenticate_method() + userinfo: dict = authenticate(username=username, password=password) + 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 diff --git a/apps/authentication/backends/oauth2/backends.py b/apps/authentication/backends/oauth2/backends.py index 755d5ef54..6469f121b 100644 --- a/apps/authentication/backends/oauth2/backends.py +++ b/apps/authentication/backends/oauth2/backends.py @@ -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 diff --git a/apps/authentication/backends/oauth2/signals.py b/apps/authentication/backends/oauth2/signals.py index 50c7837f8..b82ae90d7 100644 --- a/apps/authentication/backends/oauth2/signals.py +++ b/apps/authentication/backends/oauth2/signals.py @@ -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']) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index 03b71334f..70e0f759c 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -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 diff --git a/apps/authentication/backends/oidc/signals.py b/apps/authentication/backends/oidc/signals.py index 85d2dcd94..2f4c071e4 100644 --- a/apps/authentication/backends/oidc/signals.py +++ b/apps/authentication/backends/oidc/signals.py @@ -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']) diff --git a/apps/authentication/backends/saml2/backends.py b/apps/authentication/backends/saml2/backends.py index 70667cd94..570557c1d 100644 --- a/apps/authentication/backends/saml2/backends.py +++ b/apps/authentication/backends/saml2/backends.py @@ -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 diff --git a/apps/authentication/backends/saml2/signals.py b/apps/authentication/backends/saml2/signals.py index 42252f4d0..3dcdd9d35 100644 --- a/apps/authentication/backends/saml2/signals.py +++ b/apps/authentication/backends/saml2/signals.py @@ -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')) diff --git a/apps/authentication/signal_handlers.py b/apps/authentication/signal_handlers.py index e7be3465c..b1f55f689 100644 --- a/apps/authentication/signal_handlers.py +++ b/apps/authentication/signal_handlers.py @@ -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) diff --git a/apps/authentication/signals.py b/apps/authentication/signals.py index 0a305290c..556bba614 100644 --- a/apps/authentication/signals.py +++ b/apps/authentication/signals.py @@ -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')) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index de43b03be..4e1e63ad8 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -195,6 +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,7 +209,9 @@ 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, + # 自定义模块 + AUTH_BACKEND_CUSTOM ] AUTHENTICATION_BACKENDS_THIRD_PARTY = [AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_CAS, AUTH_BACKEND_SAML2, AUTH_BACKEND_OAUTH2] diff --git a/apps/users/migrations/0040_alter_user_source.py b/apps/users/migrations/0040_alter_user_source.py new file mode 100644 index 000000000..7d798e5c4 --- /dev/null +++ b/apps/users/migrations/0040_alter_user_source.py @@ -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'), + ), + ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 78d5eb540..6830c2ac5 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -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) From 0a66693a41d8c23d648dc444ec718c1d69623f2d Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 18 Aug 2022 09:58:32 +0800 Subject: [PATCH 10/26] =?UTF-8?q?feat:=20MongoDB=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5SSL=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applications/serializers/attrs/application_category/db.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/applications/serializers/attrs/application_category/db.py b/apps/applications/serializers/attrs/application_category/db.py index f2967963e..975979de7 100644 --- a/apps/applications/serializers/attrs/application_category/db.py +++ b/apps/applications/serializers/attrs/application_category/db.py @@ -13,3 +13,7 @@ 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( + max_length=10240, required=False, allow_null=True, label=_('CA certificate') + ) From af75b5269c02666f460c76777c37b71e3d12180a Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 23 Aug 2022 14:46:24 +0800 Subject: [PATCH 11/26] =?UTF-8?q?ca=5Fcert=E4=B8=8D=E5=81=9A=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/serializers/attrs/application_category/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/applications/serializers/attrs/application_category/db.py b/apps/applications/serializers/attrs/application_category/db.py index 975979de7..4bb862604 100644 --- a/apps/applications/serializers/attrs/application_category/db.py +++ b/apps/applications/serializers/attrs/application_category/db.py @@ -15,5 +15,5 @@ class DBSerializer(serializers.Serializer): ) use_ssl = serializers.BooleanField(default=False, label=_('Use SSL')) ca_cert = serializers.CharField( - max_length=10240, required=False, allow_null=True, label=_('CA certificate') + required=False, allow_null=True, label=_('CA certificate') ) From 9123839b48f1866fcd2193fe3a6b9191de84003f Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 24 Aug 2022 17:38:17 +0800 Subject: [PATCH 12/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=AE=A4=E8=AF=81=20backend=EF=BC=9B?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=85=B6=E4=BB=96=E8=AE=A4=E8=AF=81=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E4=BF=A1=E5=8F=B7=E8=A7=A6=E5=8F=91=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/custom.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/authentication/backends/custom.py b/apps/authentication/backends/custom.py index 4432e2919..3a0bafeb0 100644 --- a/apps/authentication/backends/custom.py +++ b/apps/authentication/backends/custom.py @@ -1,6 +1,4 @@ -import os from django.conf import settings -import inspect from django.utils.module_loading import import_string from common.utils import get_logger from django.contrib.auth import get_user_model @@ -15,6 +13,9 @@ logger = get_logger(__file__) class CustomAuthBackend(JMSModelBackend): custom_auth_method_path = 'data.auth.main.authenticate' + def load_authenticate_method(self): + return import_string(self.custom_auth_method_path) + def is_enabled(self): try: self.load_authenticate_method() @@ -35,9 +36,6 @@ class CustomAuthBackend(JMSModelBackend): ) return user, created - def load_authenticate_method(self): - return import_string(self.custom_auth_method_path) - def authenticate(self, request, username=None, password=None, **kwargs): try: authenticate = self.load_authenticate_method() From 89051b2c67f09f31ab54b81c55e6d90819c7c4be Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 24 Aug 2022 18:04:22 +0800 Subject: [PATCH 13/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=AE=A4=E8=AF=81=20backend=EF=BC=9B?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=85=B6=E4=BB=96=E8=AE=A4=E8=AF=81=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E4=BF=A1=E5=8F=B7=E8=A7=A6=E5=8F=91=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/custom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/authentication/backends/custom.py b/apps/authentication/backends/custom.py index 3a0bafeb0..d6e9cc018 100644 --- a/apps/authentication/backends/custom.py +++ b/apps/authentication/backends/custom.py @@ -29,7 +29,7 @@ class CustomAuthBackend(JMSModelBackend): @staticmethod def get_or_create_user_from_userinfo(userinfo: dict): username = userinfo['username'] - attrs = ['name', 'username', 'email'] + 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 @@ -39,7 +39,7 @@ class CustomAuthBackend(JMSModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): try: authenticate = self.load_authenticate_method() - userinfo: dict = authenticate(username=username, password=password) + userinfo: dict = authenticate(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)) From 8fc5c4cf9e418a89689fe0ef656912667c15aa26 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 24 Aug 2022 18:41:47 +0800 Subject: [PATCH 14/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=AE=A4=E8=AF=81=20backend=EF=BC=9B?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=85=B6=E4=BB=96=E8=AE=A4=E8=AF=81=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E4=BF=A1=E5=8F=B7=E8=A7=A6=E5=8F=91=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B=E9=80=9A=E8=BF=87=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/custom.py | 2 ++ apps/jumpserver/conf.py | 2 ++ apps/jumpserver/settings/auth.py | 9 +++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/authentication/backends/custom.py b/apps/authentication/backends/custom.py index d6e9cc018..d0b309b83 100644 --- a/apps/authentication/backends/custom.py +++ b/apps/authentication/backends/custom.py @@ -17,6 +17,8 @@ class CustomAuthBackend(JMSModelBackend): return import_string(self.custom_auth_method_path) def is_enabled(self): + if not settings.AUTH_CUSTOM: + return False try: self.load_authenticate_method() except Exception as e: diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 7b26b4ee2..c0aa257ee 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -224,6 +224,8 @@ class Config(dict): 'CONNECTION_TOKEN_EXPIRATION': 5 * 60, # Custom Config + 'AUTH_CUSTOM': False, + # Auth LDAP settings 'AUTH_LDAP': False, 'AUTH_LDAP_SERVER_URI': 'ldap://localhost:389', diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 4e1e63ad8..099b91de7 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -2,7 +2,6 @@ # import os import ldap -from django.utils.translation import ugettext_lazy as _ from ..const import CONFIG, PROJECT_DIR, BASE_DIR @@ -197,7 +196,6 @@ AUTH_BACKEND_OAUTH2 = 'authentication.backends.oauth2.OAuth2Backend' AUTH_BACKEND_TEMP_TOKEN = 'authentication.backends.token.TempTokenAuthBackend' AUTH_BACKEND_CUSTOM = 'authentication.backends.custom.CustomAuthBackend' - AUTHENTICATION_BACKENDS = [ # 只做权限校验 RBAC_BACKEND, @@ -210,10 +208,13 @@ 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_CUSTOM ] +AUTH_CUSTOM = CONFIG.AUTH_CUSTOM +if AUTH_CUSTOM: + # 自定义认证模块 + 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 From 1819083a25dae0a4f080b10ae71cecd69cacbbaa Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 25 Aug 2022 15:04:45 +0800 Subject: [PATCH 15/26] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20custom=20?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E6=A8=A1=E5=9D=97=E5=8A=A0=E8=BD=BD=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E5=88=A4=E6=96=ADMD5=E5=80=BC=EF=BC=8C?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E5=8F=AA=E5=8A=A0=E8=BD=BD=E4=B8=80?= =?UTF-8?q?=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/custom.py | 31 ++++++++++++-------------- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings/auth.py | 20 ++++++++++++++++- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apps/authentication/backends/custom.py b/apps/authentication/backends/custom.py index d0b309b83..c98d1742c 100644 --- a/apps/authentication/backends/custom.py +++ b/apps/authentication/backends/custom.py @@ -4,29 +4,25 @@ 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): - custom_auth_method_path = 'data.auth.main.authenticate' - - def load_authenticate_method(self): - return import_string(self.custom_auth_method_path) def is_enabled(self): - if not settings.AUTH_CUSTOM: - return False - try: - self.load_authenticate_method() - except Exception as e: - logger.warning('Not enabled custom auth backend: {}'.format(e)) - return False - else: - logger.info('Enabled custom auth backend') - return True + return settings.AUTH_CUSTOM and callable(custom_authenticate_method) @staticmethod def get_or_create_user_from_userinfo(userinfo: dict): @@ -40,8 +36,9 @@ class CustomAuthBackend(JMSModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): try: - authenticate = self.load_authenticate_method() - userinfo: dict = authenticate(username=username, password=password, **kwargs) + 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)) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index c0aa257ee..5ec858821 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -225,6 +225,7 @@ class Config(dict): # Custom Config 'AUTH_CUSTOM': False, + 'AUTH_CUSTOM_FILE_MD5': '', # Auth LDAP settings 'AUTH_LDAP': False, diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 099b91de7..4aafdd35b 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -210,8 +210,26 @@ AUTHENTICATION_BACKENDS = [ 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 -if 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) From f0dc5194238908ea0c29f85cb3fb2e3383c35de2 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 25 Aug 2022 13:42:48 +0800 Subject: [PATCH 16/26] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20windows=20ad?= =?UTF-8?q?=E5=B8=AE=E5=8A=A9=E9=93=BE=E6=8E=A5=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/const/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/const/__init__.py b/apps/common/const/__init__.py index 84a07a11a..a6bcd95c8 100644 --- a/apps/common/const/__init__.py +++ b/apps/common/const/__init__.py @@ -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 From ef4cc5f646a8437f33d13e7f090a03aec1f8d6b8 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 1 Sep 2022 16:31:20 +0800 Subject: [PATCH 17/26] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/serializers/application.py | 54 +++++--- apps/assets/serializers/account.py | 20 ++- apps/assets/task_handlers/backup/handlers.py | 124 +++++++++++++------ 3 files changed, 139 insertions(+), 59 deletions(-) diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 35a07e262..ac8f38f2a 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -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) diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index e4e320f33..9b06944c3 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -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'), diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index 311d8e395..a29058fac 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -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 From b25404cac10cc14c421f35e22853040027265bbd Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 26 Aug 2022 17:59:16 +0800 Subject: [PATCH 18/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81OAuth2=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E8=87=AA=E5=AE=9A=E4=B9=89=E6=B3=A8=E9=94=80=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/oauth2/urls.py | 3 +- apps/authentication/backends/oauth2/views.py | 34 ++++++++++++++++++-- apps/authentication/backends/saml2/views.py | 2 +- apps/authentication/views/login.py | 2 ++ apps/jumpserver/conf.py | 2 ++ apps/jumpserver/settings/auth.py | 1 + apps/jumpserver/settings/base.py | 1 + apps/settings/serializers/auth/oauth2.py | 4 +++ 8 files changed, 45 insertions(+), 4 deletions(-) diff --git a/apps/authentication/backends/oauth2/urls.py b/apps/authentication/backends/oauth2/urls.py index 94c044a7d..c44dd7c74 100644 --- a/apps/authentication/backends/oauth2/urls.py +++ b/apps/authentication/backends/oauth2/urls.py @@ -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') ] diff --git a/apps/authentication/backends/oauth2/views.py b/apps/authentication/backends/oauth2/views.py index dd295fe86..d3f4865a2 100644 --- a/apps/authentication/backends/oauth2/views.py +++ b/apps/authentication/backends/oauth2/views.py @@ -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) diff --git a/apps/authentication/backends/saml2/views.py b/apps/authentication/backends/saml2/views.py index e91fd0660..9bc3ddc97 100644 --- a/apps/authentication/backends/saml2/views.py +++ b/apps/authentication/backends/saml2/views.py @@ -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 diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 88f580279..19720b587 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -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): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 5ec858821..442089979 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -334,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': { diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 4aafdd35b..462aefc57 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -164,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 diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index a65266a1f..3c929e97a 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -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 diff --git a/apps/settings/serializers/auth/oauth2.py b/apps/settings/serializers/auth/oauth2.py index 279f3c34f..a2230750a 100644 --- a/apps/settings/serializers/auth/oauth2.py +++ b/apps/settings/serializers/auth/oauth2.py @@ -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') ) From e6d30fa77de42aec189f50da2616814befecaa70 Mon Sep 17 00:00:00 2001 From: halo Date: Thu, 1 Sep 2022 09:52:48 +0800 Subject: [PATCH 19/26] =?UTF-8?q?perf:=20telnet=E7=B3=BB=E7=BB=9F=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E8=BE=93=E5=87=BA=E4=BD=BF=E7=94=A8utf-8=E7=BC=96?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/utils/telnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/utils/telnet.py b/apps/settings/utils/telnet.py index 9785b43ae..4f03dda88 100644 --- a/apps/settings/utils/telnet.py +++ b/apps/settings/utils/telnet.py @@ -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') if __name__ == "__main__": From 409d254a2e58ddefb498b512194c7e94442f09fb Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Tue, 6 Sep 2022 10:02:51 +0800 Subject: [PATCH 20/26] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81MFA=E5=8F=AF?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8D=8E=E4=B8=BA=E4=BA=91=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/sdk/sms/endpoint.py | 1 + apps/common/sdk/sms/huawei.py | 94 +++++++++++++++++++++++++++ apps/jumpserver/conf.py | 7 ++ apps/settings/api/settings.py | 1 + apps/settings/api/sms.py | 17 +++++ apps/settings/serializers/auth/sms.py | 11 +++- 6 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 apps/common/sdk/sms/huawei.py diff --git a/apps/common/sdk/sms/endpoint.py b/apps/common/sdk/sms/endpoint.py index 3bcaa8559..044cf28a2 100644 --- a/apps/common/sdk/sms/endpoint.py +++ b/apps/common/sdk/sms/endpoint.py @@ -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') diff --git a/apps/common/sdk/sms/huawei.py b/apps/common/sdk/sms/huawei.py new file mode 100644 index 000000000..154b554fd --- /dev/null +++ b/apps/common/sdk/sms/huawei.py @@ -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 diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 442089979..f31f4250a 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -381,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': '', diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index 0f487c280..3e22b0bf8 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -40,6 +40,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'sms': serializers.SMSSettingSerializer, 'alibaba': serializers.AlibabaSMSSettingSerializer, 'tencent': serializers.TencentSMSSettingSerializer, + 'huawei': serializers.HuaweiSMSSettingSerializer, 'cmpp2': serializers.CMPP2SMSSettingSerializer, } diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 668b5ec56..301f4e203 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -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'], diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index 1278dea06..dda38b586 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -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')) From f20b465ddf5ab2ec35e882afa06ce36a0380166e Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 22 Aug 2022 17:56:56 +0800 Subject: [PATCH 21/26] =?UTF-8?q?feat:=20=E6=94=B9=E5=AF=86=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E6=94=AF=E6=8C=81MongoDB=E6=94=B9=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8232a52b8..4f2b21fb9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -139,4 +139,5 @@ ipython==8.4.0 ForgeryPy3==0.3.1 django-debug-toolbar==3.5 Pympler==1.0.1 -IPy==1.1 \ No newline at end of file +IPy==1.1 +pymongo==4.2.0 From 8e5833aef0f5f54c514225c0d2853c52ebd68adc Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Mon, 22 Aug 2022 18:17:09 +0800 Subject: [PATCH 22/26] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=80=E4=B8=8B?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 4f2b21fb9..863759fad 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -134,10 +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 -pymongo==4.2.0 From 50df7f13040890e2bc7a8d657c5cd065d6d10497 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 7 Sep 2022 10:19:23 +0800 Subject: [PATCH 23/26] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E5=BC=80=E5=90=AFssl=E4=B8=94=E8=87=AA=E7=AD=BE?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E7=9A=84db=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serializers/attrs/application_category/db.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/applications/serializers/attrs/application_category/db.py b/apps/applications/serializers/attrs/application_category/db.py index 4bb862604..d4d36e47d 100644 --- a/apps/applications/serializers/attrs/application_category/db.py +++ b/apps/applications/serializers/attrs/application_category/db.py @@ -17,3 +17,10 @@ class DBSerializer(serializers.Serializer): 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') + ) + key_file = serializers.CharField( + required=False, allow_null=True, label=_('Certificate key file') + ) + allow_invalid_cert = serializers.BooleanField(default=False, label=_('Allow invalid cert')) From 984b94c8746747ca6048388468f7dc2925d0185b Mon Sep 17 00:00:00 2001 From: jiangweidong <80373698+Hi-JWD@users.noreply.github.com> Date: Wed, 7 Sep 2022 16:08:37 +0800 Subject: [PATCH 24/26] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=BA=94=E7=94=A8ssl=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D=20(#8840)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改变量名 * 修改变量名 --- apps/applications/serializers/attrs/application_category/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/applications/serializers/attrs/application_category/db.py b/apps/applications/serializers/attrs/application_category/db.py index d4d36e47d..183397e8e 100644 --- a/apps/applications/serializers/attrs/application_category/db.py +++ b/apps/applications/serializers/attrs/application_category/db.py @@ -20,7 +20,7 @@ class DBSerializer(serializers.Serializer): client_cert = serializers.CharField( required=False, allow_null=True, label=_('Client certificate file') ) - key_file = serializers.CharField( + cert_key = serializers.CharField( required=False, allow_null=True, label=_('Certificate key file') ) allow_invalid_cert = serializers.BooleanField(default=False, label=_('Allow invalid cert')) From 56c324b04eae2b0b5b7465ae6fe724172d132da5 Mon Sep 17 00:00:00 2001 From: halo Date: Wed, 7 Sep 2022 17:37:37 +0800 Subject: [PATCH 25/26] =?UTF-8?q?perf:=20utf-8=E7=BC=96=E7=A0=81=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/utils/telnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/utils/telnet.py b/apps/settings/utils/telnet.py index 4f03dda88..b8a7e81ef 100644 --- a/apps/settings/utils/telnet.py +++ b/apps/settings/utils/telnet.py @@ -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('utf-8') + return True, output.decode('utf-8', 'ignore') if __name__ == "__main__": From 697270e3e6b00bf7a8aac2c357123e635d56225e Mon Sep 17 00:00:00 2001 From: halo Date: Thu, 8 Sep 2022 09:47:39 +0800 Subject: [PATCH 26/26] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=B8=85?= =?UTF-8?q?=E7=90=86=E4=BB=BB=E5=8A=A1=E5=81=B6=E5=8F=91=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/custom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 0bcdc77d9..1fdb530cb 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -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