From 4337389f344a000b8a78380ec071228693a26726 Mon Sep 17 00:00:00 2001 From: nannan00 <17491932+nannan00@users.noreply.github.com> Date: Thu, 17 Nov 2022 19:59:15 +0800 Subject: [PATCH 01/48] feat(api return): full error detail when auth failed --- src/api/bkuser_core/common/exception_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/bkuser_core/common/exception_handler.py b/src/api/bkuser_core/common/exception_handler.py index c6a253eb4..ec949146e 100644 --- a/src/api/bkuser_core/common/exception_handler.py +++ b/src/api/bkuser_core/common/exception_handler.py @@ -88,7 +88,7 @@ def get_ee_exception_response(exc, context, detail): elif isinstance(exc, ValidationError): data["message"] = f"validation error: {exc}" elif isinstance(exc, AuthenticationFailed): - data["message"] = "403, authentication failed" + data["message"] = f"403, authentication failed: {exc}" else: # log logger.exception("unknown exception while handling the request, detail=%s", detail) From c1c467b57f0bf464e11fea31f9f29f815a78b934 Mon Sep 17 00:00:00 2001 From: wklken Date: Fri, 18 Nov 2022 15:19:19 +0800 Subject: [PATCH 02/48] test(unittest): fix #747 (#789) * test(unittest): fix failed unittests --- .github/workflows/python-ci.yml | 1 - Makefile | 2 +- pyproject.toml | 4 +- src/api/bkuser_core/api/login/serializers.py | 2 + src/api/bkuser_core/bkiam/permissions.py | 22 +++-- .../bkuser_core/common/exception_handler.py | 74 ++++++++++++++-- .../bkuser_core/config/overlays/unittest.py | 13 --- src/api/bkuser_core/tests/apis/utils.py | 5 +- .../apis/v2/departments/test_departments.py | 21 ++--- .../tests/apis/v2/profiles/test_login.py | 9 +- .../apis/v2/profiles/test_profiles_action.py | 12 --- .../apis/v2/profiles/test_profiles_list.py | 6 +- .../bkuser_core/tests/bkiam/test_constants.py | 40 ++++----- .../bkuser_core/tests/bkiam/test_converter.py | 9 +- .../tests/bkiam/test_permissions.py | 85 +++++++++---------- .../categories/plugins/local/test_syncer.py | 7 +- src/api/bkuser_core/tests/conftest.py | 10 +++ 17 files changed, 192 insertions(+), 130 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index b38f5460c..f5587818e 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -33,4 +33,3 @@ jobs: run: pflake8 src/ --config=pyproject.toml - name: Lint with mypy run: mypy src/ --config-file=pyproject.toml - diff --git a/Makefile b/Makefile index 44b8ef5c3..5dc8f77a3 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ generate-release-md: mv src/saas/release.md docs/ test: - cd src/api && source ./test_env.sh && poetry run pytest bkuser_core/tests --disable-pytest-warnings + cd src/api && export DJANGO_SETTINGS_MODULE="bkuser_core.config.overlays.unittest" && poetry run pytest bkuser_core/tests --disable-pytest-warnings link: rm src/api/bkuser_global || true diff --git a/pyproject.toml b/pyproject.toml index 4a4e93c46..54625bcc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,8 @@ black = "^22.3.0" # isort isort = "^5.9.2" # flake8 -pyproject-flake8 = "^0.0.1-alpha.2" -flake8-comprehensions = "^3.5.0" +pyproject-flake8 = "0.0.1-alpha.2" +flake8-comprehensions = "3.5.0" # pytest pytest = "^6.2.4" pytest-django = "^3.9.0" diff --git a/src/api/bkuser_core/api/login/serializers.py b/src/api/bkuser_core/api/login/serializers.py index 662318406..ace11971f 100644 --- a/src/api/bkuser_core/api/login/serializers.py +++ b/src/api/bkuser_core/api/login/serializers.py @@ -85,6 +85,8 @@ class Meta: "status", "time_zone", "language", + "domain", + "category_id", # NOTE: 这里缩减登陆成功之后的展示字段 # "position", # "logo_url", => to logo? diff --git a/src/api/bkuser_core/bkiam/permissions.py b/src/api/bkuser_core/bkiam/permissions.py index 5d234d38c..e6c789ee9 100644 --- a/src/api/bkuser_core/bkiam/permissions.py +++ b/src/api/bkuser_core/bkiam/permissions.py @@ -41,6 +41,17 @@ def _parse_department_path(data): return field_map[the_last_of_path[0]], int(the_last_of_path[1]) +# NOTE: not used, only in unittest +CATEGORY_KEY_MAPPING = {"category.id": "id"} + +PROFILE_KEY_MAPPING = {"department._bk_iam_path_": _parse_department_path} + +DEPARTMENT_KEY_MAPPING = { + "department.id": "id", + "department._bk_iam_path_": _parse_department_path, +} + + class Permission: """ NOTE: the `operator` should be the username with domain @@ -61,7 +72,9 @@ def make_filter_of_category(self, operator: str, action_id: IAMAction): iam_request = self.helper.make_request_without_resources(username=operator, action_id=action_id) # NOTE: 这里不是给category自己用的, 而是给外检关联表用的, 所以category.id -> category_id fs = Permission().helper.iam.make_filter( - iam_request, converter_class=PathIgnoreDjangoQSConverter, key_mapping={"category.id": "category_id"} + iam_request, + converter_class=PathIgnoreDjangoQSConverter, + key_mapping={"category.id": "category_id"}, ) if not fs: raise IAMPermissionDenied( @@ -79,7 +92,7 @@ def make_filter_of_department(self, operator: str, action_id: IAMAction): fs = Permission().helper.iam.make_filter( iam_request, converter_class=PathIgnoreDjangoQSConverter, - key_mapping={"department._bk_iam_path_": _parse_department_path}, + key_mapping=PROFILE_KEY_MAPPING, ) if not fs: raise IAMPermissionDenied( @@ -96,10 +109,7 @@ def make_department_filter(self, operator: str, action_id: IAMAction): fs = Permission().helper.iam.make_filter( iam_request, converter_class=PathIgnoreDjangoQSConverter, - key_mapping={ - "department.id": "id", - "department._bk_iam_path_": _parse_department_path, - }, + key_mapping=DEPARTMENT_KEY_MAPPING, ) if not fs: raise IAMPermissionDenied( diff --git a/src/api/bkuser_core/common/exception_handler.py b/src/api/bkuser_core/common/exception_handler.py index ec949146e..06bfc62ac 100644 --- a/src/api/bkuser_core/common/exception_handler.py +++ b/src/api/bkuser_core/common/exception_handler.py @@ -11,11 +11,14 @@ import logging import traceback +from django.conf import settings from django.core.exceptions import PermissionDenied +from django.db import ProgrammingError from django.http import Http404 from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import AuthenticationFailed, ValidationError from rest_framework.response import Response +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR from rest_framework.views import exception_handler from sentry_sdk import capture_exception @@ -59,11 +62,11 @@ def custom_exception_handler(exc, context): # do nothing if get extra details fail pass - # if bool(context["request"].META.get(settings.FORCE_RAW_RESPONSE_HEADER)): - # return get_raw_exception_response(exc, context, detail) - # else: - # return get_ee_exception_response(exc, context, detail) - return get_ee_exception_response(exc, context, detail) + # NOTE: raw response还有在用, 并且单测基于raw response判断的status_code和异常报错(所以不能去掉) + if bool(context["request"].META.get(settings.FORCE_RAW_RESPONSE_HEADER)): + return get_raw_exception_response(exc, context, detail) + else: + return get_ee_exception_response(exc, context, detail) def get_ee_exception_response(exc, context, detail): @@ -111,3 +114,64 @@ def get_ee_exception_response(exc, context, detail): response = Response(data=data, status=EE_GENERAL_STATUS_CODE) setattr(response, "from_exception", True) return response + + +def one_line_error(detail): + """Extract one line error from error dict""" + try: + # A bare ValidationError will result in a list "detail" field instead of a dict + if isinstance(detail, list): + return detail[0] + else: + key, (first_error, *_) = next(iter(detail.items())) + if key == "non_field_errors": + return first_error + return f"{key}: {first_error}" + except Exception: # pylint: disable=broad-except + return "参数格式错误" + + +def get_raw_exception_response(exc, context, detail): + if isinstance(exc, ValidationError): + data = { + "code": "VALIDATION_ERROR", + "detail": one_line_error(exc.detail), + "fields_detail": exc.detail, + } + return Response(data, status=exc.status_code, headers={}) + elif isinstance(exc, CoreAPIError): + data = { + "code": exc.code.code_name, + "detail": exc.code.message, + } + return Response(data, status=exc.code.status_code, headers={}) + elif isinstance(exc, IAMPermissionDenied): + data = {"code": "PERMISSION_DENIED", "detail": exc.extra_info} + return Response(data, status=exc.status_code, headers={}) + elif isinstance(exc, ProgrammingError): + logger.exception("occur some programming errors") + data = {"code": "PROGRAMMING_ERROR", "detail": UNKNOWN_ERROR_HINT} + return Response(data, status=HTTP_400_BAD_REQUEST, headers={}) + + # log + logger.exception("unknown exception while handling the request, detail=%s", detail) + # report to sentry + capture_exception(exc) + + # Call REST framework's default exception handler to get the standard error response. + response = exception_handler(exc, context) + # Use a default error code + if response is not None: + response.data.update(code="ERROR") + setattr(response, "from_exception", True) + return response + + # NOTE: 不暴露给前端, 只打日志, 所以不放入data.detail + # error detail + if exc is not None: + detail["error"] = traceback.format_exc() + + data = {"result": False, "data": detail, "code": -1, "message": UNKNOWN_ERROR_HINT} + response = Response(data=data, status=HTTP_500_INTERNAL_SERVER_ERROR) + setattr(response, "from_exception", True) + return diff --git a/src/api/bkuser_core/config/overlays/unittest.py b/src/api/bkuser_core/config/overlays/unittest.py index 8980b9746..e21f6eb4f 100644 --- a/src/api/bkuser_core/config/overlays/unittest.py +++ b/src/api/bkuser_core/config/overlays/unittest.py @@ -73,19 +73,6 @@ def get_loggers(package_name: str, log_level: str) -> dict: # patch the unittest logging loggers LOGGING["loggers"] = get_loggers("bkuser_core", LOG_LEVEL) -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(PROJECT_ROOT, "db.sqlite3"), - # "NAME": env(f"{db_prefix}_NAME"), - # "USER": env(f"{db_prefix}_USER"), - # "PASSWORD": env(f"{db_prefix}_PASSWORD"), - # "HOST": env(f"{db_prefix}_HOST"), - # "PORT": env(f"{db_prefix}_PORT"), - # "OPTIONS": {"charset": "utf8mb4"}, - "TEST": {"CHARSET": "utf8mb4", "COLLATION": "utf8mb4_general_ci"}, - } -} # ============================================================================== # Test Ldap diff --git a/src/api/bkuser_core/tests/apis/utils.py b/src/api/bkuser_core/tests/apis/utils.py index 711d62893..d7760c6f1 100644 --- a/src/api/bkuser_core/tests/apis/utils.py +++ b/src/api/bkuser_core/tests/apis/utils.py @@ -18,10 +18,11 @@ def get_api_factory(force_params: dict = None): force_params = force_params or {} normal_params = { "HTTP_FORCE_RAW_RESPONSE": True, - "HTTP_RAW_USERNAME": True, "Content-Type": "application/json", - "HTTP_AUTHORIZATION": f"iBearer {list(settings.INTERNAL_AUTH_TOKENS.keys())[0]}", "HTTP_X_BKUSER_OPERATOR": "tester", + # "HTTP_RAW_USERNAME": True, + # should be removed after enhanced_account removed the token auth + "HTTP_AUTHORIZATION": f"iBearer {list(settings.INTERNAL_AUTH_TOKENS.keys())[0]}", } normal_params.update(force_params) diff --git a/src/api/bkuser_core/tests/apis/v2/departments/test_departments.py b/src/api/bkuser_core/tests/apis/v2/departments/test_departments.py index 5f9c4da88..d02b7b659 100644 --- a/src/api/bkuser_core/tests/apis/v2/departments/test_departments.py +++ b/src/api/bkuser_core/tests/apis/v2/departments/test_departments.py @@ -349,23 +349,22 @@ def view(self): return DepartmentViewSet.as_view({"get": "get_profiles", "post": "add_profiles"}) @pytest.mark.parametrize( - "lookup_value, creating_list, raw_username, expected", + "lookup_value, creating_list, expected", [ ( "部门C", ["部门A", "部门B", "部门C"], - False, "@test", ), - ( - "部门C", - ["部门A", "部门B", "部门C"], - True, - "user-0", - ), + # ( + # "部门C", + # ["部门A", "部门B", "部门C"], + # True, + # "user-0", + # ), ], ) - def test_department_get_profiles_1_cate(self, view, lookup_value, creating_list, raw_username, expected): + def test_department_get_profiles_1_cate(self, view, lookup_value, creating_list, expected): """测试从部门获取人员(当只有一个默认目录时)""" parent_id = 1 @@ -386,9 +385,7 @@ def test_department_get_profiles_1_cate(self, view, lookup_value, creating_list, attach_pd_relation(profile=_p, department=target_dep) response = view( - request=get_api_factory({"HTTP_RAW_USERNAME": raw_username}).get( - f"/api/v2/departments/{lookup_value}/profiles/?lookup_field=name" - ), + request=get_api_factory().get(f"/api/v2/departments/{lookup_value}/profiles/?lookup_field=name"), lookup_value=lookup_value, ) assert len(response.data["results"]) == 11 diff --git a/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py b/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py index 82180be0b..01bb5ce48 100644 --- a/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py +++ b/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py @@ -28,7 +28,8 @@ class TestListCreateApis: @pytest.fixture(scope="class") def factory(self): - return get_api_factory({"HTTP_RAW_USERNAME": False}) + # return get_api_factory({"HTTP_RAW_USERNAME": False}) + return get_api_factory() @pytest.fixture(scope="class") def check_view(self): @@ -47,11 +48,11 @@ def required_return_key(self): return [ "username", "email", - "telephone", - "wx_userid", + # "telephone", + # "wx_userid", "domain", "status", - "staff_status", + # "staff_status", ] def _assert_required_keys_exist(self, response_data: dict): diff --git a/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_action.py b/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_action.py index f0b609f45..a15844172 100644 --- a/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_action.py +++ b/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_action.py @@ -81,18 +81,6 @@ def test_profile_retrieve_domain(self, factory, view): response = view(request=request, lookup_value="adminAb@lettest") assert response.data["username"] == "adminAb@lettest" - def test_profile_retrieve_no_domain(self, factory, view): - """测试强制用户名不返回 domain""" - factory = get_api_factory() - make_simple_profile( - username="adminAb", - force_create_params={"domain": "lettest", "category_id": 2}, - ) - request = factory.get("/api/v2/profiles/adminAb@lettest/") - # 测试时需要手动指定 kwargs 参数当作路径参数 - response = view(request=request, lookup_value="adminAb@lettest") - assert response.data["username"] == "adminAb" - # --------------- update --------------- @pytest.mark.parametrize( "former_passwords,new_password,expected", diff --git a/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_list.py b/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_list.py index df1c201f7..1ef143d35 100644 --- a/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_list.py +++ b/src/api/bkuser_core/tests/apis/v2/profiles/test_profiles_list.py @@ -277,7 +277,7 @@ def test_profile_username_with_domain( @pytest.mark.parametrize( "query_string,target_code,results_count,target_username", [ - ("?wildcard_search=lette&wildcard_search_fields=domain", 200, 1, "adminAb"), + ("?wildcard_search=lette&wildcard_search_fields=domain", 200, 1, "adminAb@lettest"), ("?exact_lookups=admin", 200, 1, "admin"), ], ) @@ -300,9 +300,9 @@ def test_profile_username_force_no_domain( "query_string,results_count,target_username", [ ("?ordering=create_time", 2, "admin"), - ("?ordering=-create_time", 2, "adminAb"), + ("?ordering=-create_time", 2, "adminAb@lettest"), ("?ordering=id", 2, "admin"), - ("?ordering=-id", 2, "adminAb"), + ("?ordering=-id", 2, "adminAb@lettest"), ], ) def test_profile_list_ordering(self, factory, view, query_string, results_count, target_username): diff --git a/src/api/bkuser_core/tests/bkiam/test_constants.py b/src/api/bkuser_core/tests/bkiam/test_constants.py index 003db369b..6a11a1f27 100644 --- a/src/api/bkuser_core/tests/bkiam/test_constants.py +++ b/src/api/bkuser_core/tests/bkiam/test_constants.py @@ -18,28 +18,28 @@ class TestResourceTypeEnum: - @pytest.mark.parametrize( - "is_leaf, path, f, v", - [ - (True, "/category,5/department,3440/department,3443/", "parent_id", 3443), - (False, "/category,5/department,3440/department,3443/", "id", 3443), - (True, "/category,5/", "category_id", 5), - (False, "/category,5/", "category_id", 5), - (True, "/department,3440/department,3443/", "parent_id", 3443), - (False, "/department,3440/department,3443/", "id", 3443), - ], - ) - def test_get_key_mapping(self, is_leaf, path, f, v): - key_mapping = ResourceType.get_key_mapping(ResourceType.DEPARTMENT) - path_method = key_mapping["department._bk_iam_path_"] + # @pytest.mark.parametrize( + # "is_leaf, path, f, v", + # [ + # (True, "/category,5/department,3440/department,3443/", "parent_id", 3443), + # (False, "/category,5/department,3440/department,3443/", "id", 3443), + # (True, "/category,5/", "category_id", 5), + # (False, "/category,5/", "category_id", 5), + # (True, "/department,3440/department,3443/", "parent_id", 3443), + # (False, "/department,3440/department,3443/", "id", 3443), + # ], + # ) + # def test_get_key_mapping(self, is_leaf, path, f, v): + # key_mapping = ResourceType.get_key_mapping(ResourceType.DEPARTMENT) + # path_method = key_mapping["department._bk_iam_path_"] - data = {"value": path} - if not is_leaf: - data["node_type"] = "non-leaf" + # data = {"value": path} + # if not is_leaf: + # data["node_type"] = "non-leaf" - f, v = path_method(data) - assert f == f - assert v == v + # f, v = path_method(data) + # assert f == f + # assert v == v @pytest.mark.parametrize( "dep_chain, expected", diff --git a/src/api/bkuser_core/tests/bkiam/test_converter.py b/src/api/bkuser_core/tests/bkiam/test_converter.py index 03d965f11..87750f1e3 100644 --- a/src/api/bkuser_core/tests/bkiam/test_converter.py +++ b/src/api/bkuser_core/tests/bkiam/test_converter.py @@ -10,8 +10,8 @@ """ import pytest -from bkuser_core.bkiam.constants import ResourceType from bkuser_core.bkiam.converters import PathIgnoreDjangoQSConverter +from bkuser_core.bkiam.permissions import CATEGORY_KEY_MAPPING, DEPARTMENT_KEY_MAPPING class TestPathIgnoreDjangoQSConverter: @@ -82,7 +82,8 @@ class TestPathIgnoreDjangoQSConverter: ) def test_converter(self, policies, expected): """测试 filter 转换""" - fs = PathIgnoreDjangoQSConverter(ResourceType.get_key_mapping(ResourceType.DEPARTMENT)).convert(policies) + # fs = PathIgnoreDjangoQSConverter(ResourceType.get_key_mapping(ResourceType.DEPARTMENT)).convert(policies) + fs = PathIgnoreDjangoQSConverter(DEPARTMENT_KEY_MAPPING).convert(policies) assert str(fs) == expected @pytest.mark.parametrize( @@ -96,5 +97,7 @@ def test_converter(self, policies, expected): ) def test_converter_other(self, policies, expected): """测试 filter 转换""" - fs = PathIgnoreDjangoQSConverter(ResourceType.get_key_mapping(ResourceType.CATEGORY)).convert(policies) + + # fs = PathIgnoreDjangoQSConverter(ResourceType.get_key_mapping(ResourceType.CATEGORY)).convert(policies) + fs = PathIgnoreDjangoQSConverter(CATEGORY_KEY_MAPPING).convert(policies) assert str(fs) == expected diff --git a/src/api/bkuser_core/tests/bkiam/test_permissions.py b/src/api/bkuser_core/tests/bkiam/test_permissions.py index e09894944..06aed8f85 100644 --- a/src/api/bkuser_core/tests/bkiam/test_permissions.py +++ b/src/api/bkuser_core/tests/bkiam/test_permissions.py @@ -8,15 +8,12 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -from unittest import mock +# from unittest import mock import pytest -from bkuser_core.bkiam.constants import IAMAction -from bkuser_core.bkiam.permissions import IAMPermissionExtraInfo -from bkuser_core.departments.models import Department from bkuser_core.departments.v2.views import DepartmentViewSet -from bkuser_core.tests.apis.utils import get_api_factory, make_request_operator_aware +from bkuser_core.tests.apis.utils import get_api_factory pytestmark = pytest.mark.django_db @@ -30,50 +27,50 @@ def factory(self): def view(self): return DepartmentViewSet.as_view({"get": "list", "post": "create"}) - def test_from_request(self, factory, view): - """测试从 request 生成 info 对象""" - request = factory.get("/api/v2/departments/") - make_request_operator_aware(request, operator="tester") - info = IAMPermissionExtraInfo.from_request(request) + # def test_from_request(self, factory, view): + # """测试从 request 生成 info 对象""" + # request = factory.get("/api/v2/departments/") + # make_request_operator_aware(request, operator="tester") + # info = IAMPermissionExtraInfo.from_request(request) - assert info.auth_infos - assert info.auth_infos[0].id == "manage_department" - assert info.auth_infos[0].display_name == IAMAction.get_choice_label(IAMAction.MANAGE_DEPARTMENT) + # assert info.auth_infos + # assert info.auth_infos[0].id == "manage_department" + # assert info.auth_infos[0].display_name == IAMAction.get_choice_label(IAMAction.MANAGE_DEPARTMENT) - def test_from_request_obj(self, factory, view): - """测试从 request 和 鉴权对象 生成 info 对象""" - request = factory.patch("/api/v2/departments/1/") - make_request_operator_aware(request, operator="tester") - info = IAMPermissionExtraInfo.from_request(request, obj=Department.objects.get(id=1)) + # def test_from_request_obj(self, factory, view): + # """测试从 request 和 鉴权对象 生成 info 对象""" + # request = factory.patch("/api/v2/departments/1/") + # make_request_operator_aware(request, operator="tester") + # info = IAMPermissionExtraInfo.from_request(request, obj=Department.objects.get(id=1)) - assert info.auth_infos - assert info.auth_infos[0].id == "manage_department" - assert info.auth_infos[0].display_name == IAMAction.get_choice_label(IAMAction.MANAGE_DEPARTMENT) - assert info.auth_infos[0].related_resources[0].name == "总公司" - assert info.auth_infos[0].related_resources[0].id == "1" - assert info.auth_infos[0].related_resources[0].type == "department" + # assert info.auth_infos + # assert info.auth_infos[0].id == "manage_department" + # assert info.auth_infos[0].display_name == IAMAction.get_choice_label(IAMAction.MANAGE_DEPARTMENT) + # assert info.auth_infos[0].related_resources[0].name == "总公司" + # assert info.auth_infos[0].related_resources[0].id == "1" + # assert info.auth_infos[0].related_resources[0].type == "department" - def test_to_dict(self, factory, view): - """测试从对象生成 dict""" - request = factory.get("/api/v2/departments/") - make_request_operator_aware(request, operator="tester") + # def test_to_dict(self, factory, view): + # """测试从对象生成 dict""" + # request = factory.get("/api/v2/departments/") + # make_request_operator_aware(request, operator="tester") - def return_fake_callback_url(*arg, **kwargs): - return "http://test.com" + # def return_fake_callback_url(*arg, **kwargs): + # return "http://test.com" - with mock.patch("bkuser_core.bkiam.helper.IAMHelper.generate_callback_url") as mocked_func: - mocked_func.side_effect = return_fake_callback_url - info = IAMPermissionExtraInfo.from_request(request) + # with mock.patch("bkuser_core.bkiam.helper.IAMHelper.generate_callback_url") as mocked_func: + # mocked_func.side_effect = return_fake_callback_url + # info = IAMPermissionExtraInfo.from_request(request) - raw_info = info.to_dict() + # raw_info = info.to_dict() - assert raw_info == { - "auth_infos": [ - { - "display_name": IAMAction.get_choice_label(IAMAction.MANAGE_DEPARTMENT), - "id": "manage_department", - "related_resources": [], - } - ], - "callback_url": return_fake_callback_url(), - } + # assert raw_info == { + # "auth_infos": [ + # { + # "display_name": IAMAction.get_choice_label(IAMAction.MANAGE_DEPARTMENT), + # "id": "manage_department", + # "related_resources": [], + # } + # ], + # "callback_url": return_fake_callback_url(), + # } diff --git a/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py b/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py index d73d0a48d..e9ff9f281 100644 --- a/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py +++ b/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py @@ -182,9 +182,12 @@ def test_sync_leaders(self, syncer, users, make_parser_set, titles, expected, le ) def test_sync_wrong_users(self, syncer, users, make_parser_set, titles, expected): """测试异常用户同步""" - syncer._sync_users(make_parser_set(titles), users) + # FIXME: assert exception + with pytest.raises(Exception) as exc_info: + syncer._sync_users(make_parser_set(titles), users) assert ( - set(Profile.objects.filter(category_id=syncer.category_id).values_list("username", flat=True)) == expected + "导入执行完成: 成功 1 条记录, 失败 2 条记录" in exc_info.value.args[0] + or "导入执行完成: 成功 0 条记录, 失败 1 条记录" in exc_info.value.args[0] ) diff --git a/src/api/bkuser_core/tests/conftest.py b/src/api/bkuser_core/tests/conftest.py index 4fc7e07f7..a3a32eab1 100644 --- a/src/api/bkuser_core/tests/conftest.py +++ b/src/api/bkuser_core/tests/conftest.py @@ -25,6 +25,16 @@ pytestmark = pytest.mark.django_db +@pytest.fixture(autouse=True) +def reset_cache(): + yield + + # 必须清缓存, locmem放置了一些数据会导致测试互相影响 + from django.core.cache import caches + + caches["locmem"].clear() + + @pytest.fixture def factory(): return get_api_factory() From 54456c2c39363b6932f82d1e34e50371d38acb4b Mon Sep 17 00:00:00 2001 From: wklken Date: Mon, 21 Nov 2022 15:37:30 +0800 Subject: [PATCH 03/48] feat(src/build): merge build files from another repo fix #773 --- pyproject.toml | 5 +- src/build/README.md | 1 + src/build/api/VERSION | 1 + src/build/api/manage.py | 22 +++ src/build/api/projects.yaml | 7 + src/build/api/support-files/bkiam/.gitkeeper | 0 .../sql/0002_usermgr_20191128_mysql.sql | 1 + .../#etc#supervisor-usermgr-api.conf | 56 +++++++ .../api#bkuser_core#config#overlays#prod.py | 146 ++++++++++++++++++ src/build/saas/.env | 6 + src/build/saas/bk_user_manage.png | Bin 0 -> 8386 bytes src/build/saas/manage.py | 33 ++++ .../saas/support-files/bk_user_manage.ini | 33 ++++ src/build/saas/support-files/supervisord.conf | 21 +++ 14 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 src/build/README.md create mode 100644 src/build/api/VERSION create mode 100644 src/build/api/manage.py create mode 100644 src/build/api/projects.yaml create mode 100644 src/build/api/support-files/bkiam/.gitkeeper create mode 100644 src/build/api/support-files/sql/0002_usermgr_20191128_mysql.sql create mode 100644 src/build/api/support-files/templates/#etc#supervisor-usermgr-api.conf create mode 100644 src/build/api/support-files/templates/api#bkuser_core#config#overlays#prod.py create mode 100644 src/build/saas/.env create mode 100644 src/build/saas/bk_user_manage.png create mode 100644 src/build/saas/manage.py create mode 100644 src/build/saas/support-files/bk_user_manage.ini create mode 100644 src/build/saas/support-files/supervisord.conf diff --git a/pyproject.toml b/pyproject.toml index 54625bcc1..47c47931b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ exclude = ''' | .+/migrations | .+/sdk | .+/node_modules + | .+/build )/ ''' @@ -59,7 +60,7 @@ format = "pylint" show_source = "true" statistics = "true" count = "true" -exclude = "*migrations*,*.pyc,.git,__pycache__,*/node_modules/*,*/templates_module*,*/bin/*,*/config/*,*sdk*" +exclude = "*migrations*,*.pyc,.git,__pycache__,*/node_modules/*,*/templates_module*,*/bin/*,*/config/*,*sdk*,*build*" [tool.mypy] ignore_missing_imports = true @@ -70,6 +71,7 @@ pretty=true exclude = '''(?x)( instrumentor\.py$ | otel\.py$ + | src/build/.*\.py$ )''' [[tool.mypy.overrides]] @@ -78,5 +80,6 @@ module = [ "*.config.*", "bkuser_sdk.*", "*.bkuser_sdk.*", + "*.build.*", ] ignore_errors = true diff --git a/src/build/README.md b/src/build/README.md new file mode 100644 index 000000000..4bd8957e6 --- /dev/null +++ b/src/build/README.md @@ -0,0 +1 @@ +NOTE: currently this dir is only for build binary packages! not for the container images! diff --git a/src/build/api/VERSION b/src/build/api/VERSION new file mode 100644 index 000000000..4640e9fac --- /dev/null +++ b/src/build/api/VERSION @@ -0,0 +1 @@ +${VERSION} diff --git a/src/build/api/manage.py b/src/build/api/manage.py new file mode 100644 index 000000000..97b91836a --- /dev/null +++ b/src/build/api/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bkuser_core.config.overlays.prod") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django # noqa + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/src/build/api/projects.yaml b/src/build/api/projects.yaml new file mode 100644 index 000000000..9e90e2cb8 --- /dev/null +++ b/src/build/api/projects.yaml @@ -0,0 +1,7 @@ +- name: api + module: usermgr + project_dir: usermgr/api + alias: usermgrapi + language: python/3.6 + version: ${VERSION} + version_type: ee/ce diff --git a/src/build/api/support-files/bkiam/.gitkeeper b/src/build/api/support-files/bkiam/.gitkeeper new file mode 100644 index 000000000..e69de29bb diff --git a/src/build/api/support-files/sql/0002_usermgr_20191128_mysql.sql b/src/build/api/support-files/sql/0002_usermgr_20191128_mysql.sql new file mode 100644 index 000000000..862a4c95b --- /dev/null +++ b/src/build/api/support-files/sql/0002_usermgr_20191128_mysql.sql @@ -0,0 +1 @@ +CREATE DATABASE IF NOT EXISTS `bk_user` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/src/build/api/support-files/templates/#etc#supervisor-usermgr-api.conf b/src/build/api/support-files/templates/#etc#supervisor-usermgr-api.conf new file mode 100644 index 000000000..4e89f32dd --- /dev/null +++ b/src/build/api/support-files/templates/#etc#supervisor-usermgr-api.conf @@ -0,0 +1,56 @@ +[unix_http_server] +file=__BK_HOME__/logs/usermgr/supervisor.sock + +[supervisord] +logfile=__BK_HOME__/logs/usermgr/supervisord.log +logfile_maxbytes=50MB +logfile_backups=10 +loglevel=info +pidfile=__BK_HOME__/logs/usermgr/supervisord.pid +nodaemon=false +minfds=1024 +minprocs=200 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix://__BK_HOME__/logs/usermgr/supervisor.sock + +[program:usermgrapi] +directory=__BK_HOME__/usermgr/api +environment= + PATH="__BK_HOME__/.envs/usermgr-api/bin/", + DJANGO_SETTINGS_MODULE="bkuser_core.config.overlays.prod", + LOGS_PATH="__BK_HOME__/logs", +command=__BK_HOME__/.envs/usermgr-api/bin/gunicorn wsgi -w 16 --max-requests 1024 --max-requests-jitter 50 --threads 2 --worker-class gevent -b :8009 --access-logfile - --error-logfile - +autostart=true +autorestart=true +redirect_stderr=true +stdout_logfile=__BK_HOME__/logs/usermgr/stdout.log + + +[program:usermgrapi-worker] +directory=__BK_HOME__/usermgr/api +environment= + PATH="__BK_HOME__/.envs/usermgr-api/bin/", + DJANGO_SETTINGS_MODULE="bkuser_core.config.overlays.prod", + LOGS_PATH="__BK_HOME__/logs", +command=__BK_HOME__/.envs/usermgr-api/bin/celery -A bkuser_core worker -l info --concurrency=8 --max-tasks-per-child=1 +autostart=true +autorestart=true +redirect_stderr=true +stdout_logfile=__BK_HOME__/logs/usermgr/worker.log + + +[program:usermgrapi-beat] +directory=__BK_HOME__/usermgr/api +environment= + PATH="__BK_HOME__/.envs/usermgr-api/bin/", + DJANGO_SETTINGS_MODULE="bkuser_core.config.overlays.prod", + LOGS_PATH="__BK_HOME__/logs", +command=__BK_HOME__/.envs/usermgr-api/bin/celery -A bkuser_core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler +autostart=true +autorestart=true +redirect_stderr=true +stdout_logfile=__BK_HOME__/logs/usermgr/beat.log diff --git a/src/build/api/support-files/templates/api#bkuser_core#config#overlays#prod.py b/src/build/api/support-files/templates/api#bkuser_core#config#overlays#prod.py new file mode 100644 index 000000000..81485800b --- /dev/null +++ b/src/build/api/support-files/templates/api#bkuser_core#config#overlays#prod.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +""" +正式环境配置 +""" +import urllib.parse + +from bkuser_core.config.common.django_basic import * # noqa +from bkuser_core.config.common.logging import * # noqa +from bkuser_core.config.common.system import * # noqa + +from bkuser_global.logging import LoggingType, get_logging + +DEBUG = False + +# use the static root 'static' in production envs +if not DEBUG: + STATIC_ROOT = "static" + +APP_ID = "__BK_USERMGR_APP_CODE__" +APP_TOKEN = "__BK_USERMGR_APP_SECRET__" + +# 数据库配置信息 +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", # 默认用mysql + "NAME": "bk_user", + "USER": "__BK_USERMGR_MYSQL_USER__", + "PASSWORD": "__BK_USERMGR_MYSQL_PASSWORD__", + "HOST": "__BK_USERMGR_MYSQL_HOST__", + "PORT": "__BK_USERMGR_MYSQL_PORT__", + } +} + +LOGGING_DIR = "__BK_HOME__/logs/usermgr/" +LOGGING = get_logging( + logging_type=LoggingType.FILE, + log_level=LOG_LEVEL, + package_name="bkuser_core", + formatter="verbose", + logging_dir=LOGGING_DIR, + file_name="api", +) + +# 初始化用户名、密码 +SUPERUSER_USERNAME = "__BK_PAAS_ADMIN_USERNAME__" +SUPERUSER_PASSWORD = "__BK_PAAS_ADMIN_PASSWORD__" + +# domain +PAAS_DOMAIN = "__BK_PAAS_PUBLIC_ADDR__" +BK_PAAS_URL = "__BK_PAAS_PUBLIC_URL__" +BK_COMPONENT_API_URL = "__BK_PAAS_PUBLIC_URL__" + +# cookie访问域 +BK_COOKIE_DOMAIN = ".__BK_DOMAIN__" + +SECRET_KEY = "__BK_PAAS_ESB_SECRET_KEY__" + +# ESB Token +ESB_TOKEN = "__BK_PAAS_APP_SECRET__" + +# license +CERTIFICATE_DIR = "__BK_CERT_PATH__" +CERTIFICATE_SERVER_DOMAIN = "__BK_LICENSE_PRIVATE_ADDR__" + +CACHES = { + "default": { + "BACKEND": "bkuser_core.common.cache.DummyRedisCache", + }, + 'locmem': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'memory_cache_0', + }, +} +# 全局缓存过期时间,默认为一小时 +GLOBAL_CACHES_TIMEOUT = env.int("GLOBAL_CACHES_TIMEOUT", default=60 * 60) + +# 快捷单元测试 dummy cache 标记 +USE_DUMMY_CACHE_FOR_TEST = True + +FORCE_JSONP_HEADER = "HTTP_FORCE_JSONP" +FORCE_NO_CACHE_HEADER = "HTTP_FORCE_NO_CACHE" + +########## +# Celery # +########## +REDIS_URL = "" +REDIS_KEY_PREFIX = env("CACHE_REDIS_KEY_PREFIX", default="bk-user-") +CELERY_BROKER_URL = "amqp://__BK_USERMGR_RABBITMQ_USERNAME__:__BK_USERMGR_RABBITMQ_PASSWORD__@__BK_USERMGR_RABBITMQ_HOST__:__BK_USERMGR_RABBITMQ_PORT__/__BK_USERMGR_RABBITMQ_VHOST__" +CELERY_RESULT_BACKEND = "amqp://__BK_USERMGR_RABBITMQ_USERNAME__:__BK_USERMGR_RABBITMQ_PASSWORD__@__BK_USERMGR_RABBITMQ_HOST__:__BK_USERMGR_RABBITMQ_PORT__/__BK_USERMGR_RABBITMQ_VHOST__" + +# ============================================================================== +# IAM +# ============================================================================== +def get_iam_config(app_id: str, app_token: str) -> dict: + return dict( + api_host="__BK_IAM_PRIVATE_URL__", + system_id=env("BK_IAM_SYSTEM_ID", default="bk_usermgr"), + # iam app 访问 url 用于回调拼接 + iam_app_host=env( + "BK_IAM_SAAS_HOST", + default=f"{BK_PAAS_URL}/o/{env('BK_IAM_V3_APP_CODE', default='bk_iam')}", + ), + apply_path="apply-custom-perm", + # 自己的 app_id & app_token + own_app_id=app_id, + own_app_token=app_token, + ) + + +IAM_CONFIG = get_iam_config(APP_ID, APP_TOKEN) # type: ignore +ENABLE_IAM = True + +# 请求 ESB API 默认版本号 +DEFAULT_BK_API_VER = "v2" + +# 与 SaaS 约定的权限校验头,未传递时跳过权限校验 +NEED_IAM_HEADER = "HTTP_NEED_IAM" +ACTION_ID_HEADER = "HTTP_ACTION_ID" + +# =============================================================================== +# API 访问限制(暂未开启) +# =============================================================================== +INTERNAL_AUTH_TOKENS = {"TCwCnoiuUgPccj8y0Wx187vJBqzqddfLlm": {"username": "iadmin"}} +ACCESS_APP_WHITE_LIST = {"bk-iam": "lLP3gabV8M0C9vbwHQwzSYJX3WumcJsDSdVNQtq6FJVCLqJX6o"} + + +# ============================================================================== +# 登陆相关 +# ============================================================================== +LOGIN_REDIRECT_TO = f"{BK_PAAS_URL}/login/?c_url={SITE_URL}" + +# ============================================================================== +# SaaS 配置 +# ============================================================================== +# SaaS 应用 Code +SAAS_CODE = "bk_user_manage" +# SaaS 请求地址,用于拼接访问地址(默认支持二进制部署) +SAAS_URL = env( + "SAAS_URL", default=urllib.parse.urljoin(BK_PAAS_URL, f"/o/{SAAS_CODE}/") +) + +# SaaS 偏好 client ip 头 +CLIENT_IP_FROM_SAAS_HEADER = "HTTP_CLIENT_IP_FROM_SAAS" + +# 可通过 SaaS 管理的用户目录类型 +CAN_MANUAL_WRITE_LISTS = ["local"] diff --git a/src/build/saas/.env b/src/build/saas/.env new file mode 100644 index 000000000..772a776d4 --- /dev/null +++ b/src/build/saas/.env @@ -0,0 +1,6 @@ +BK_PAAS_URL=$BK_PAAS_HOST +BK_LOGIN_API_URL=http://paas.service.consul:80/login + +BK_APP_CODE=$APP_ID +BK_APP_SECRET=$APP_TOKEN +DB_USER=$DB_USERNAME diff --git a/src/build/saas/bk_user_manage.png b/src/build/saas/bk_user_manage.png new file mode 100644 index 0000000000000000000000000000000000000000..a66df3b79ca5535bb875c1b70ec3bc5e3ec858d8 GIT binary patch literal 8386 zcmaJ{cQ{<#)*p=M(TOg)FvDPUQAZagdT*nK87-qk2|)-ULRW~_~w1@eed`Eai8xz&pBtGeOBAQwbp+2I{PFT-P58XXC(&!08~2K8YcLA@LwnC z4g6KkReBD8V+n*=2Hr=w1)`k%;Q&=vgfpC5$H&PXZUT354e=X=D*^ySPdrU611$~o zJPskde77))Z0bQl}Gt5w_>n7UVslg(1|)bd`6L7IzYtgi3(qWSyKqa6Ff|adn6fLMpC{^`QU=U>hLBnJNBKdA-x#pjbhK5>q@xN`si_5~dcRny?rA9iG^ z?292+4%0s-eool0>Ma-3>yYY@^77pbAR`jUE)gGAiKbWAW>FQ-E@o##i@)`|6D3Ug zlv}@$l>5o(?Foa4yeJ|%(%Y_2o~VrIiX<%BH#aM|qpq%le%&)Pqsd7DH z*Gi(lvJ>(KPJ=Mo=KHUQrW^m%9$+TUTl2w<0-I+e%MEY(Mw+fPFT>gj zBxh+W3$+{-8Xb~%u0<3%onvx|+E2v@ny>oPdSmJOi2HV4_Y!9J+e#B!vi>%>(zeBXSqiYFAQP6TE{aeF7j)(N)TK{WfH{Luyb5>GaLpN(I|Czo+AO$EEH<}mOe7nTFBhRqZ~oyo zW*k#}g}KjQ3vfCdT-fQv4R9WlBY_uQKP==ojGd#Q<-u)(JK0+KOwCT&^T{f=>hez? zp)?pAJK4;gls`ze+XrWv3F8O@7AP6F?Nk>h8?9rxNnil`=a;ssMWXpAm~61~g#dRhRQI~hkgeP$H*5w>`R>g?%<0O>Z z+b!4O`60{S4h}uV9=6#KJQ}ZT{loa#PBs-o0XwMwh$sV$zL^9V_Fc7^^`R&BE=fYN zI_6it%sYI~cLoNtoaR(x?X>!w*QKIF&fwd2Db~Z1xYZu}5~neO@`!IMSiAjUG%_lL zd29kbC2K2x*@NaM%xY50b9sUKSo>3q7?-%;)cRdh|GhKe*+TV}-5bpeHWimVR9BQu zFp(I46f@%_x|R~N`;ayoLlJQRK6;F@>l1!o*>v%|gbXVOI|3&`*Wv3S7K7UU_q({? zp*dS$UVfT={8~MOg}z4~BT~6U&46S2L>&WaUl2$G%Dn7H7mr@VU~C}kP)=4-tdTH= zf+VDEHF$YNEe*8}cgEP5%-Uug_|htd143kQ!lMef(9W1UBWM`YB-sJjlm$l~tp_?Q z+q8R-45btOPTGg@gNma&ZrrS*?jL*#tlI=EEPpO=19rL24elDms~qgxw2EyIc)v?N(aIay=8D{2PFs0H(xGGPmn${QQYx~@!lFrp z>(h^?JF3B-k7Qjl=z4=Tkv2$T1Pw_PH9KzZ*Ce9uahRdk<`NRxcH$A_@XMa-5tGa< zm%Di1V{xm=9ekg0ox>8BL|GCG7VP%a(*+-mPp~F7PapOLRJ06)edQL)P+s)m;o|Duso6w_ zD_*<3g=B#1<&n7Qgwz#Rhwb3UyK;u|kS!^wHDYj$sT>nSEDX z+oLB#tMjv`z>2kyQ?))Px>)fy%RSecR8SfMV6_+}iWxRd z?3oZv6Z57m9=kx+D&7NRb2LDZcVGLyxiiYRkJ?)=A$)|N_|9(+b{O)sk1$kaZopKo zKONEDMBXsgco3q(6C*@N{ar;QuHGh7Kg$`D${n2)O$iOABwpZEk|DXoJ*$8@`n{0Z zU~}NxOoVp*>3s6%3!z?4hU_+Q-#-mIc8)%_nv4Eb$QGzddt>o=k?7jBALaT+K{NwT zwuVtvMH8_8hA0Pgv3BJN{j2R^AdQEAGuZ;G&Pk8@S-W)Mm0=p@!9ykeXQ9;3?qWbU zd(gQ*5{rz7N-EmlW*dbylG@W=!?bP46~|?kbV^wvynur zOX1b)#B*w;5r&hvbXO`5ML`|ETSJ{pDZ8Pg%X{$@H>+ZLUNfCA2wdLnjtq}vy_!)< zYbL@^Ss~MH;Ef{FG*rSa)7x0=D@5y&DuYkB2h~Pr1YjMfGe8=xkY*NUGF9dB{%Ia; zzgL?0-Qjm4*%gBN^2cnpL8O6pDoUJ>=l*=^NSGDZt&kfm2J?ZqUQ zms`89x_9xl{pRnhbLw-aFh$-+mw^F)=A#8F$r#MD&c5JQyR`hxi0&HD&GUa+jXe>s zv3foJmeLh_u6)wBL+URIBdV+Hb$eGiC;C}}Ve*^itDDC(B-WcMIQFUDl;vLo<*I(E zDH@|a?2FKzH{$$N4J+&k);;UL+}8S&PS8+QX_4t=J0Gcy+z^es;TOuCU{ zE(ioC|5mPNi`r|X)J?ik|C+*b1*R0_$m(^=Hq?UwoX;8iit02_rGDYV*P6RYnZHHC z=u1466-Lclrc-Yi6I*PLPk%f=;9l!&uzJ_ojr0d{u5!j33SX4Ij7KH&tC>_`MA9_4 zzje7el_1}p&w~6ck6`3R>H`fdw(-tEgxhH>w80lY$o89gbJ+XJG^1Xx(8iIZct5VV z=NlhlP@V(MW%cf4>J&&sP#2Ba$NSEdkmhKY9Es%`PcvV*8r?t|80wTAf@SyXuVQX` zlJ;8NA1Xz$CH^SMjYncJy!{Me#$H@!p%iP~!R$#CA%@)SI-Wgcd&u__kJR6Z z(rVh@^Tg<@yjg`%+Qbi@oz)(++LlR*WsMGoWU+8+e|gQPxomj#d8m0Xh6u$|F(ljm z^3vF>c&oRO-S*hfB7f~h&ia?Cb;^ce%wjh~ zD4x>3m)ON{P2p*oi6hC1ML{!k`l1ojG|w!%x&+B^MPA#+cgPi@O=KO*bqIiCT_bJ1 zTW{wijE7H423WDh!Y>rbnz!H*r$W7HGtpqK!s>_`LE&SL9mCpB3t!rdhbA7EZQUbO z8~Pr2`7HI5{4SB2^7+PF$|9*Ka&Rmg<4zQD-gVRa6Xa^C^*;KNjz7eUukvH%DTemeOK1OGR^UlPiyg=RBDxLD3I#``$X@>rEn4M%uNgA#qFlNtd9FBlk%KCiCyQZQe$15!u*&6I!IWo z$vu25)sX${7@u^cO%;*i=pkxksB0v z6`Kr|Qy(X@rN(%7C)k!)`Z|~!2^5=YPx#UiW!sjt6#vE$rq_L2yT40hRHey;Q1Nfn zpz4OiDl8qBJ(&hUmhafG$-CBj+E=lsH@|wgqyM}!MuqOeQ?yRtNAhQH8FXsZYLPIX z8KomJ0icvJth6DJ?idu_o&{-tZYLe z$B1;UZqci(^p7v|nvTAhcX+4wlW9&-i;GX|z#l4Cleg?-sh5270KL3T*O=Tl#p5j{ zWL1?{FE6l$Pqj-j+vstu(|UK(b=|6-oUBi(!wIsy-f*2M)-x>;S#FnsNJflC=V%|I z=&L?!9lSyv5phHu)U%LczIXBdWPi^>r8e_PA6Q?^2u1w)o)5&dD{P9im!=`E`EG>G zrWkkyI(~M1;T$yf(17V_25#|*%E1d1hwMrZhk=Kp20#E=Ar0Ky$^Wn*>E5!yZWg)r z8^0Ji=lRCgj`B(9yWTVOlhLZ{n=&9^6gIEmo_^U0c59QV!U99R2W5x8-7n1I@&*~oZ;q8mK%!E7sCSw;qZNYFG+rE}W1(Z; zThcDxQuZb9k`#`YTxcwxD=X==yV-GJ7Sp#F^ZdYFsSeLTo&6%GF2f1@*3wiJN4+O8 zs3WOT1jd{F(a$jfvBE*$WcPO6=ib;fzYH_|YAYJs#2GNlPX1Ks#<{Vm zqn=d3Ncp_pqe*^g)GbZRi73FlN#G3@!E6Vp@>QI<%YVcn}@^mm@S|(@3S%o$6KIPqkGn_8p-$rJgeu@Ri5Y z%v<;K375~IZI*LCilQ@Pyc@uyQN zx>Utc?IgVY=;4a!FDp9w)Pi$>?hZk+NF|Z)=Prg{N!5v>=W?ZEiHXe}IqYK8bBR@l zh{zOmsM2X1VKyGH%>%!1VdAuzq=&q2$0LQGx3)M#EP9x7n1dcYvuqvB)uv{d30Wt? zk&9kW^osV~#bJeyR9zYfA~h@&$KB85+{|czIrj21xdbu0I8S&`_~Bbo?>Qap*zsL{ zv4R>yrP_=8$o-4ETO zx1JXA;XglBIMzh8{F1_(do|tP{0xo2foBeUXD2L2*oXt)2)-J9gRmMsvJ|Z%Mf+|1f zFXMvm)R(}S^5x%BJ=Yk&*olYsgOz){5h5W+G zNe@X(iPU)CU|RG}zkU==MBVc~>xug>{wTt5Pa_qkBG|S8G{t)M3x4u5h$kTN`LZPY zIxeCrLrSs3=gCO=5lGh?nN=7Wwz0+JfwFXIVlg}URjwIa5n!VEbdATZAxAxFT7m9W zhM{oJ#uOsj#Yk_h#WS_$;ZS*+`cmmpiV|6Ntjr2A{jO;Eh8BwY$Zs@zu^GQS=@yRv z`0n_ zAPEa?Y3bqszcwXlK42f0KJHt8@<6UN4sB0rOZc&w1U1}Yfz%AU!i9IqkQIzbu0h9z z5?utUEY~O5+Wdd=m7{rueYQWw_D9P%LU7~`l6qqiz--1k$NMj0|-}Tr?+?>KAv$#-c^z-#WYXc|Mo4kYvk5z72 z%oL5_sLLHqWJyJgd`6^DARoz?IUkUxA97Lo!5?)6#)I&^XTU5%7J?u-l^g@DahBP< zdmXmlg{f@$ahCvU_WAy-le#6%w9ujRh%@?Xr=#c!+Y9laO+Sf+r`BojCjG3siuTIC zk?Oo5)$(4dHBI19RQ=>^y_1;9#j0QPEP=|qX7N>DS(R{GcwI*|zF-keq5g=IzjP&6!k5 zLzhA+W!TqAU8)Q;26j(73=~^D?N>~fBAZ_~ORg~mh$N#xYkWJ7@sSGdYy)w<$EV((2hNMQ!<@XN z04}W`#k&Z;6LnOTN9>?fQ&Gtdpp3|!2Li_t){&; zDBTx3nO0Ec+(|fn#4-Le(M%R+IyUV*yg}qCllWGN7vIO7qpc(b=d_6NZuqi5!3Ma`3zvd0>i6`H1)4XNG3r2@q>uFRjK>VT48 zZ)UCp*oQ0!=XLIuiY*$>+Eq{RZ20}oydB)FK#*mS(rf%ljfCB@v)uiZAFKT9`97JK zd^^J+H?p~DvKC9eU;m{_P_gO0@aRU+huJ4&9>9T-?6*Vg8EDPuw9Y&Om5-%~Z(HOL z{v4vxaQ{2Aq=Q+;)&=in`UR5?IILWu46nBC+d2kjK^0Vybz!Foc;G0`IVSe|(dL`_ zN^yGLw(nSiuqLaW6D?fWcp}fQNx;ooGAG$?5M>?Q{$4`ndd1TSH+yp(B-WHVX z>_B6&XG=5_-jw|>wHsVV1psZ_m2A$Q!2ywUr-d!mi?4dq`N_iBl{oO1RA@J=#g0zKJ6yzk~AXXQtfX9En-dNa;ms#jt z&;|%B-EbsaC#x$07?OUK0YY!4xiRb+jA6)|*is6H=NsU%k~s%D-ity&0e;Q2oI6ud zcjFVS++yzxqX`Xa1;)*MBysgGvAe@n!Ymvfx&yaopaelFeKhxvCI{Y&I?Qc?taXh) z9nI!xmHv4nG4k}JweENlbdsk(pHo_%vG#}`25cd}KtGhd5x!}sYB3hnNQ)+Bv+^yr z(oFx#aGyJBi@uk4gEJE$RJBJ^TW1FHq-bh1ZaC^QBbeGVt;SjB%yJ$n(Y}(`Q%8j} zY;S!2;eSxy$~7?vu3g9*d>zcZCTbL;LF6&-!WWe6)`ao=Wu%i^oL z$46^@&FrHW{)hEN;c9>$!5oJD!O@7LdK$4TcamwyFnaiHrs=V_*#O!;m_?VwBJf|G zr>yU@aa%F<_+1Gx&5VKXgbse|i#E)sP`XNQ3*~J#T%z`q0!_xF`u^_En3}ecyKmm$NV9zIhlhReg=D6Z&f#;i4DaZ#^7~ z5;2pq0OCx-NXyzpNE*(Ea~p8iV~6!zA{fr#^|H5I7t<$^N$}&Jro!PnV4A9-q>HquEmcI>=*)R9~=cW+zSPEa58RMfsMW8^q6+)0hYBnzPUX*yivv&I_=Wg364X(S6#28 z!O>~%@U~}$jg#z|a&da5&19@Nj6_hMN*iKqCVII(+leVxYDrd_%Z2MVQMlcTSfD>c zfjKR{_NAJHFyB1%6~^e@s>NmlYW>k-e0`2LK7z&@rU(0a7*o(clI!09aOM&TsoqlC zQm2wo(a`XT|))aN4{1$s_lGZJa4kH6k$}Wk6@#Ry&&2v{|xU zBU!yrfI|Ay5pMIq?1y)6%N#G%boM#mx(7f6h|HlTv-@`T_iqI{uzMQyYL3zW1Nwx4 A!2kdN literal 0 HcmV?d00001 diff --git a/src/build/saas/manage.py b/src/build/saas/manage.py new file mode 100644 index 000000000..a25794224 --- /dev/null +++ b/src/build/saas/manage.py @@ -0,0 +1,33 @@ +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import os +import sys + +if __name__ == "__main__": + + # manage.py used to work in dev env + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bkuser_shell.config.overlays.prod") + + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django # noqa + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/src/build/saas/support-files/bk_user_manage.ini b/src/build/saas/support-files/bk_user_manage.ini new file mode 100644 index 000000000..a17e15c22 --- /dev/null +++ b/src/build/saas/support-files/bk_user_manage.ini @@ -0,0 +1,33 @@ +[uwsgi] +socket = /data/app/run/uwsgi.sock +pidfile2 = /data/app/run/uwsgi.pid +logto2 = /data/app/logs/bk_user_manage/uwsgi.log +single-interpreter = True + +logdate = true +log-format = [%(addr)] [%(ctime)] [%(method)] [%(uri)] [%(proto)] [%(status)] [%(msecs)] [%(referer)] [%(uagent)] + +memory-report = true + +master = true +vacuum = true + +chdir = /data/app/code/ +module = wsgi:application + +cheaper = 4 +cheaper-initial = 4 + +workers = 16 + +cheaper-algo = busyness +cheaper-overload = 5 +cheaper-step = 2 +cheaper-busyness-multiplier = 60 + +buffer-size = 8192 +post-buffering = 8192 + +max-requests = 1024 +mount = /o/bk_user_manage=wsgi.py +manage-script-name = true \ No newline at end of file diff --git a/src/build/saas/support-files/supervisord.conf b/src/build/saas/support-files/supervisord.conf new file mode 100644 index 000000000..86d86addc --- /dev/null +++ b/src/build/saas/support-files/supervisord.conf @@ -0,0 +1,21 @@ +[unix_http_server] +file = {{.app_container_path}}run/supervisord.sock + +[supervisorctl] +configuration = {{.app_container_path}}supervisord.conf +serverurl = unix://{{.app_container_path}}/run/supervisord.sock + +[supervisord] +pidfile = {{.app_container_path}}run/supervisord.pid +logfile = {{.app_container_path}}logs/{{.app_code}}/supervisord.log +directory = {{.app_container_path}} + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[program: {{.app_code}}_uwsgi] +command = /cache/.bk/env/bin/uwsgi --ini {{.app_container_path}}code/support-files/{{.app_code}}.ini +stdout_logfile = {{.app_container_path}}logs/{{.app_code}}/uwsgi.log +redirect_stderr = true +autorestart = true +environment = {{.environment}} \ No newline at end of file From fcdc18cd357c4cfd1b916e416e2aee1daa4b8228 Mon Sep 17 00:00:00 2001 From: yuri Date: Mon, 21 Nov 2022 16:06:44 +0800 Subject: [PATCH 04/48] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20xss=20?= =?UTF-8?q?=E6=94=BB=E5=87=BB=E6=BC=8F=E6=B4=9E=20#796?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/package.json | 3 ++- src/pages/src/main.js | 11 +++++++++++ .../src/views/organization/details/UserMaterial.vue | 2 +- src/pages/src/views/organization/table/UserTable.vue | 2 +- src/pages/src/views/organization/tree/TreeSearch.vue | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/pages/package.json b/src/pages/package.json index 3bf9d2f45..4460c5058 100644 --- a/src/pages/package.json +++ b/src/pages/package.json @@ -69,7 +69,8 @@ "vue-i18n": "8.9.0", "vue-image-crop-upload": "2.5.0", "vue-router": "3.0.6", - "vuex": "3.1.1" + "vuex": "3.1.1", + "xss": "1.0.13" }, "devDependencies": { "@babel/core": "7.11.0", diff --git a/src/pages/src/main.js b/src/pages/src/main.js index b2fd6d802..918b408f8 100644 --- a/src/pages/src/main.js +++ b/src/pages/src/main.js @@ -25,6 +25,7 @@ import methods from '@/plugins/methods'; import bus from '@/common/bus'; import cursor from '@/directives/cursor'; import { Base64 } from 'js-base64'; +import xss from 'xss'; Vue.component(VueCropper); Vue.use(vClickOutside); @@ -34,6 +35,16 @@ Vue.directive('cursor', cursor); Vue.config.devtools = true; Vue.prototype.$bus = new Vue(); Vue.use(Base64); +Vue.prototype.$xss = (html) => { + const attrs = ['class', 'title', 'target', 'style', 'src', 'onerror']; + return xss(html || '', { + onTagAttr: (tag, name, value) => { + if (attrs.includes(name)) { + return `${name}=${value}`; + } + }, + }); +}; injectCSRFTokenToHeaders(); window.bus = bus; diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index 558da3f1f..b4518dbf8 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -88,7 +88,7 @@

{{phoneNumber}}

-

{{fieldInfo.value || '--'}}

+

{{$xss(fieldInfo.value) || '--'}}

diff --git a/src/pages/src/views/organization/table/UserTable.vue b/src/pages/src/views/organization/table/UserTable.vue index 1ea670869..97b4aad34 100644 --- a/src/pages/src/views/organization/table/UserTable.vue +++ b/src/pages/src/views/organization/table/UserTable.vue @@ -74,7 +74,7 @@
- {{getValueByType(key, item[key]) || '--'}} + {{$xss(getValueByType(key, item[key])) || '--'}}
diff --git a/src/pages/src/views/organization/tree/TreeSearch.vue b/src/pages/src/views/organization/tree/TreeSearch.vue index 6af976662..455521787 100644 --- a/src/pages/src/views/organization/tree/TreeSearch.vue +++ b/src/pages/src/views/organization/tree/TreeSearch.vue @@ -110,7 +110,7 @@ @click="handleSelect(item)">

- {{ item.username + '(' + item.display_name + ') ' }} + {{ item.username + '(' + $xss(item.display_name) + ') ' }} {{item.category_name}}

{{ getUserDetail(item) }}

From cb57bff1acd9438d15130ce6d4226d76d8b5733e Mon Sep 17 00:00:00 2001 From: wklken Date: Mon, 21 Nov 2022 16:47:53 +0800 Subject: [PATCH 05/48] fix(profile/create): fail when got no settings of expired_after_days fix #757 --- src/api/bkuser_core/api/web/profile/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api/bkuser_core/api/web/profile/views.py b/src/api/bkuser_core/api/web/profile/views.py index 3b7d13569..679e93567 100644 --- a/src/api/bkuser_core/api/web/profile/views.py +++ b/src/api/bkuser_core/api/web/profile/views.py @@ -292,7 +292,12 @@ def create(self, request, *args, **kwargs): meta = SettingMeta.objects.filter( key="expired_after_days", namespace=SettingsEnableNamespaces.ACCOUNT.value ).first() - expired_after_days = Setting.objects.filter(category_id=category_id, meta=meta).first().value + + # NOTE: maybe None here if the meta is not set + expired_after_days = -1 + expired_after_days_setting = Setting.objects.filter(category_id=category_id, meta=meta).first() + if expired_after_days_setting: + expired_after_days = expired_after_days_setting.value # 账户有效期,不传,默认设置为目录设置项 if expired_after_days == -1: From 1b8a27baedcac01fb3c8ca45e661b8affd53d638 Mon Sep 17 00:00:00 2001 From: wklken Date: Tue, 22 Nov 2022 10:31:18 +0800 Subject: [PATCH 06/48] feat(wsgi/faulthandler): enable faulthandler in wsgi.py (#805) * feat(wsgi/faulthandler): enable faulthandler in wsgi.py fix #770 * feat(remove): config.json for bk-user-sdk --- src/api/wsgi.py | 3 +++ src/config.json | 5 ----- src/login/wsgi.py | 3 +++ src/saas/wsgi.py | 3 +++ 4 files changed, 9 insertions(+), 5 deletions(-) delete mode 100644 src/config.json diff --git a/src/api/wsgi.py b/src/api/wsgi.py index 2fe6df59c..e8fd974f7 100644 --- a/src/api/wsgi.py +++ b/src/api/wsgi.py @@ -8,10 +8,13 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +import faulthandler import os from django.core.wsgi import get_wsgi_application +faulthandler.enable() + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bkuser_core.config.overlays.prod") application = get_wsgi_application() diff --git a/src/config.json b/src/config.json deleted file mode 100644 index 99ec87d62..000000000 --- a/src/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "packageName":"bkuser_sdk", - "projectName":"bkuser_sdk", - "packageVersion":"1.0.0" -} \ No newline at end of file diff --git a/src/login/wsgi.py b/src/login/wsgi.py index 7c07f0317..c1417195a 100755 --- a/src/login/wsgi.py +++ b/src/login/wsgi.py @@ -10,11 +10,14 @@ specific language governing permissions and limitations under the License. """ +import faulthandler import os from dj_static import Cling from django.core.wsgi import get_wsgi_application +faulthandler.enable() + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bklogin.config.prod") application = Cling(get_wsgi_application()) diff --git a/src/saas/wsgi.py b/src/saas/wsgi.py index afdc45c9b..064f8bad3 100644 --- a/src/saas/wsgi.py +++ b/src/saas/wsgi.py @@ -8,10 +8,13 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +import faulthandler import os from django.core.wsgi import get_wsgi_application +faulthandler.enable() + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bkuser_shell.config.overlays.prod") application = get_wsgi_application() From d1b2905f91f838113cd2ae78e8de5f368a22b58c Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 23 Nov 2022 11:06:37 +0800 Subject: [PATCH 07/48] fix(audit/export): export fail when got ugettext_lazy fix #812 --- src/api/bkuser_core/api/web/audit/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/bkuser_core/api/web/audit/serializers.py b/src/api/bkuser_core/api/web/audit/serializers.py index 77c95305b..5b91a922c 100644 --- a/src/api/bkuser_core/api/web/audit/serializers.py +++ b/src/api/bkuser_core/api/web/audit/serializers.py @@ -132,7 +132,8 @@ def get_reason(self, obj) -> Optional[str]: """get reason display name""" if obj.is_success: return None - return LOGIN_FAILED_REASON_MAP.get(obj.reason, _("未知失败原因")) + # bugfix: ugettext_lazy + return str(LOGIN_FAILED_REASON_MAP.get(obj.reason, _("未知失败原因"))) def get_datetime(self, obj): return obj.create_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ") From 12b07d663608d26a8dd7b71f4750fabcacfb3d66 Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Wed, 23 Nov 2022 11:36:31 +0800 Subject: [PATCH 08/48] =?UTF-8?q?fix:=20xss=20=E5=86=99=E5=85=A5=E6=97=B6?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/src/views/organization/details/InputString.vue | 1 + src/pages/src/views/organization/details/UserMaterial.vue | 2 +- src/pages/src/views/organization/table/UserTable.vue | 2 +- src/pages/src/views/organization/tree/TreeSearch.vue | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/src/views/organization/details/InputString.vue b/src/pages/src/views/organization/details/InputString.vue index a73ed18b2..978bd6083 100644 --- a/src/pages/src/views/organization/details/InputString.vue +++ b/src/pages/src/views/organization/details/InputString.vue @@ -59,6 +59,7 @@ export default { methods: { // 失焦验证 verifyInput(item) { + item.value = this.$xss(item.value); if (!item.require) { return; } diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index b4518dbf8..558da3f1f 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -88,7 +88,7 @@

{{phoneNumber}}

-

{{$xss(fieldInfo.value) || '--'}}

+

{{fieldInfo.value || '--'}}

diff --git a/src/pages/src/views/organization/table/UserTable.vue b/src/pages/src/views/organization/table/UserTable.vue index 97b4aad34..1ea670869 100644 --- a/src/pages/src/views/organization/table/UserTable.vue +++ b/src/pages/src/views/organization/table/UserTable.vue @@ -74,7 +74,7 @@
- {{$xss(getValueByType(key, item[key])) || '--'}} + {{getValueByType(key, item[key]) || '--'}}
diff --git a/src/pages/src/views/organization/tree/TreeSearch.vue b/src/pages/src/views/organization/tree/TreeSearch.vue index 455521787..6af976662 100644 --- a/src/pages/src/views/organization/tree/TreeSearch.vue +++ b/src/pages/src/views/organization/tree/TreeSearch.vue @@ -110,7 +110,7 @@ @click="handleSelect(item)">

- {{ item.username + '(' + $xss(item.display_name) + ') ' }} + {{ item.username + '(' + item.display_name + ') ' }} {{item.category_name}}

{{ getUserDetail(item) }}

From edb7de23b612460eb30d0e39b37e2a515120b88e Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Fri, 25 Nov 2022 10:31:59 +0800 Subject: [PATCH 09/48] =?UTF-8?q?fix:=20=E7=94=A8=E6=88=B7=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E8=BD=AC=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/src/views/organization/details/InputString.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/src/views/organization/details/InputString.vue b/src/pages/src/views/organization/details/InputString.vue index 978bd6083..377beadfd 100644 --- a/src/pages/src/views/organization/details/InputString.vue +++ b/src/pages/src/views/organization/details/InputString.vue @@ -59,7 +59,7 @@ export default { methods: { // 失焦验证 verifyInput(item) { - item.value = this.$xss(item.value); + item.value = item.value.replace(//g, '>'); if (!item.require) { return; } From 003ae1946db25c078e1c466a4373216b982431d7 Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Mon, 5 Dec 2022 16:29:17 +0800 Subject: [PATCH 10/48] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=81=8C?= =?UTF-8?q?=E5=8A=A1=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98=20#820?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/src/views/organization/details/UserMaterial.vue | 2 +- src/pages/src/views/organization/index.vue | 1 + src/pages/src/views/organization/table/UserTable.vue | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index 558da3f1f..45152055c 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -223,7 +223,7 @@ export default { this.activeFieldsList.forEach((item) => { if (item.options.length > 0) { item.options.map((key) => { - if (key.id === this.currentProfile[item.key]) { + if (key.id === this.currentProfile[item.key] || key.id === Number(this.currentProfile[item.key])) { item.value = key.value; } }); diff --git a/src/pages/src/views/organization/index.vue b/src/pages/src/views/organization/index.vue index e68cede79..5e38f3c05 100644 --- a/src/pages/src/views/organization/index.vue +++ b/src/pages/src/views/organization/index.vue @@ -557,6 +557,7 @@ export default { this.filterTreeData(catalog, this.treeDataList); catalog.children = catalog.departments; catalog.children.forEach((department) => { + this.$set(department, 'category_id', catalog.id); this.filterTreeData(department, catalog, catalog.type === 'local'); }); }); diff --git a/src/pages/src/views/organization/table/UserTable.vue b/src/pages/src/views/organization/table/UserTable.vue index 1ea670869..20a64de37 100644 --- a/src/pages/src/views/organization/table/UserTable.vue +++ b/src/pages/src/views/organization/table/UserTable.vue @@ -263,7 +263,7 @@ export default { return ''; } for (let i = 0; i < options.length; i++) { - if (options[i].id === value) { + if (options[i].id === value || options[i].id === Number(value)) { if (this.$i18n.locale === 'en') { return value; } From 48a686fa6723baae6177b565b707a2e44be9c6dc Mon Sep 17 00:00:00 2001 From: Canway-shiisa <90179140+Canway-shiisa@users.noreply.github.com> Date: Tue, 6 Dec 2022 11:49:04 +0800 Subject: [PATCH 11/48] =?UTF-8?q?feat:=20=E6=9C=AC=E5=9C=B0=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E5=AF=BC=E5=85=A5excel=E6=94=AF=E6=8C=81=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E6=9B=B4=E6=96=B0=20#728=20(#813)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 本地目录导入excel支持选择更新 #728 --- .../api/web/category/serializers.py | 4 ++ src/api/bkuser_core/api/web/category/views.py | 9 ++- .../categories/plugins/local/syncer.py | 58 ++++++++++++++----- .../categories/plugins/local/test_syncer.py | 9 +-- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/api/bkuser_core/api/web/category/serializers.py b/src/api/bkuser_core/api/web/category/serializers.py index ed7978a44..97d037d06 100644 --- a/src/api/bkuser_core/api/web/category/serializers.py +++ b/src/api/bkuser_core/api/web/category/serializers.py @@ -192,6 +192,10 @@ class CategoryFileImportInputSLZ(serializers.Serializer): file = serializers.FileField(required=False) +class CategoryFileImportQuerySLZ(serializers.Serializer): + is_overwrite = serializers.BooleanField(required=False, default=False) + + class CategorySyncResponseOutputSLZ(serializers.Serializer): task_id = serializers.CharField(help_text="task_id for the sync job.") diff --git a/src/api/bkuser_core/api/web/category/views.py b/src/api/bkuser_core/api/web/category/views.py index 0750866ed..7f1a04eb0 100644 --- a/src/api/bkuser_core/api/web/category/views.py +++ b/src/api/bkuser_core/api/web/category/views.py @@ -25,6 +25,7 @@ CategoryExportInputSLZ, CategoryExportProfileOutputSLZ, CategoryFileImportInputSLZ, + CategoryFileImportQuerySLZ, CategoryMetaOutputSLZ, CategoryNamespaceSettingUpdateInputSLZ, CategoryProfileListInputSLZ, @@ -454,6 +455,9 @@ def post(self, request, *args, **kwargs): def _local_category_do_import(self, request, instance): """向本地目录导入数据文件""" + query_slz = CategoryFileImportQuerySLZ(data=request.query_params) + query_slz.is_valid(raise_exception=True) + slz = CategoryFileImportInputSLZ(data=request.data) slz.is_valid(raise_exception=True) @@ -465,7 +469,10 @@ def _local_category_do_import(self, request, instance): raise error_codes.CREATE_SYNC_TASK_FAILED.f(str(e)) instance_id = instance.id - params = {"raw_data_file": slz.validated_data["file"]} + params = { + "raw_data_file": slz.validated_data["file"], + "is_overwrite": query_slz.validated_data["is_overwrite"], + } try: # TODO: FileField 可能不能反序列化, 所以可能不能传到 celery 执行 adapter_sync(instance_id, operator=request.operator, task_id=task_id, **params) diff --git a/src/api/bkuser_core/categories/plugins/local/syncer.py b/src/api/bkuser_core/categories/plugins/local/syncer.py index 2f3e33484..f4d7e3b92 100644 --- a/src/api/bkuser_core/categories/plugins/local/syncer.py +++ b/src/api/bkuser_core/categories/plugins/local/syncer.py @@ -125,13 +125,13 @@ def __post_init__(self): self._default_password_valid_days = int(ConfigProvider(self.category_id).get("password_valid_days")) self.fetcher: ExcelFetcher = self.get_fetcher() - def sync(self, raw_data_file): + def sync(self, raw_data_file, is_overwrite): user_rows, departments = self.fetcher.fetch(raw_data_file) with transaction.atomic(): self._sync_departments(departments) with transaction.atomic(): - self._sync_users(self.fetcher.parser_set, user_rows) + self._sync_users(self.fetcher.parser_set, user_rows, is_overwrite) self._sync_leaders(self.fetcher.parser_set, user_rows) self._notify_init_passwords() @@ -175,7 +175,35 @@ def _judge_data_all_none(raw_data: list) -> bool: """某些状况下会读取 Excel 整个空行""" return all(x is None for x in raw_data) - def _sync_users(self, parser_set: "ParserSet", users: list): + def _department_profile_relation_handle( + self, is_overwrite, department_groups, profile_id, should_deleted_department_profile_relation_ids + ): + cell_parser = DepartmentCellParser(self.category_id) + # 已存在的用户-部门关系 + old_department_profile_relations = DepartmentThroughModel.objects.filter(profile_id=profile_id) + # Note: 有新关系可能存在重复数据,所以这里使用不变的old_department_set用于后续判断是否存在的依据, + # 而不使用后面会变更的old_department_relations数据 + old_department_set = set([r.department_id for r in old_department_profile_relations]) + old_department_relations = {r.department_id: r.id for r in old_department_profile_relations} + + for department in cell_parser.parse_to_db_obj(department_groups): + # 用户-部门关系已存在 + if department.pk in old_department_set: + # Note: 可能本次更新里存在重复数据,dict无法重复移除 + if department.pk in old_department_relations: + del old_department_relations[department.pk] + continue + + # 不存在则添加 + department_attachment = DepartmentThroughModel(department_id=department.pk, profile_id=profile_id) + self.db_sync_manager.magic_add(department_attachment) + + # 已存在的数据从old_department_relations移除后,最后剩下的数据,表示多余的,即本次更新里不存在的用户部门关系 + # 如果是覆盖,则记录需要删除多余数据 + if is_overwrite and len(old_department_relations) > 0: + should_deleted_department_profile_relation_ids.extend(old_department_relations.values()) + + def _sync_users(self, parser_set: "ParserSet", users: list, is_overwrite: bool = False): """在内存中操作&判断数据,bulk 插入""" logger.info("=========== trying to load profiles into memory ===========") @@ -184,6 +212,7 @@ def _sync_users(self, parser_set: "ParserSet", users: list): success_count = 0 total = len(users) + should_deleted_department_profile_relation_ids = [] for index, user_raw_info in enumerate(users): if self._judge_data_all_none(user_raw_info): logger.debug("empty line, skipping") @@ -235,8 +264,13 @@ def _sync_users(self, parser_set: "ParserSet", users: list): progress(index, total, f"loading {username}") try: updating_profile = Profile.objects.get(username=username, category_id=self.category_id) - - # 如果已经存在,则更新该 profile + # 已存在的用户:如果未勾选 <进行覆盖更新>(即is_overwrite为false)=》则忽略,反之则更新该 profile + if not is_overwrite: + logger.debug( + "username %s exist, and is_overwrite is false, so will not do update for this user, skip", + username, + ) + continue for name, value in profile_params.items(): if name == "extras": extras = updating_profile.extras or {} @@ -250,6 +284,7 @@ def _sync_users(self, parser_set: "ParserSet", users: list): setattr(updating_profile, name, value) profile_id = updating_profile.id + self.db_sync_manager.magic_add(updating_profile, SyncOperation.UPDATE.value) logger.debug("(%s/%s) username<%s> already exist, trying to update it", username, index + 1, total) @@ -278,15 +313,12 @@ def _sync_users(self, parser_set: "ParserSet", users: list): # 2 获取关联的部门DB实例,创建关联对象 progress(index, total, "adding profile & department relation") department_groups = parser_set.get_cell_data("department_name", user_raw_info) + self._department_profile_relation_handle( + is_overwrite, department_groups, profile_id, should_deleted_department_profile_relation_ids + ) - cell_parser = DepartmentCellParser(self.category_id) - for department in cell_parser.parse_to_db_obj(department_groups): - relation_params = {"department_id": department.pk, "profile_id": profile_id} - try: - DepartmentThroughModel.objects.get(**relation_params) - except DepartmentThroughModel.DoesNotExist: - department_attachment = DepartmentThroughModel(**relation_params) - self.db_sync_manager.magic_add(department_attachment) + if len(should_deleted_department_profile_relation_ids) > 0: + DepartmentThroughModel.objects.filter(id__in=should_deleted_department_profile_relation_ids).delete() # 需要在处理 leader 之前全部插入 DB self.db_sync_manager[Profile].sync_to_db() diff --git a/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py b/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py index e9ff9f281..7d2d7be8b 100644 --- a/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py +++ b/src/api/bkuser_core/tests/categories/plugins/local/test_syncer.py @@ -105,7 +105,7 @@ def test_sync_users(self, syncer, pre_create_department, users, make_parser_set, ) @pytest.mark.parametrize( - "users,titles,expected", + "users,titles,expected,is_overwrite", [ ( [ @@ -118,10 +118,11 @@ def test_sync_users(self, syncer, pre_create_department, users, make_parser_set, "bbbb": "xxxx@xxxx.xyz", "cccc": "cccc@xxxx.com", }, - ), + True, + ) ], ) - def test_update_existed_users(self, syncer, users, make_parser_set, titles, expected): + def test_update_existed_users(self, syncer, users, make_parser_set, titles, expected, is_overwrite): """测试更新已存在用户""" for u in ["aaaa", "cccc"]: a = make_simple_profile(username=u, force_create_params={"category_id": syncer.category_id}) @@ -130,7 +131,7 @@ def test_update_existed_users(self, syncer, users, make_parser_set, titles, expe # TODO: 当前 id 最大值是在 db_sync_manager 初始化时确定的,实际上并不科学 syncer.db_sync_manager._update_cache() - syncer._sync_users(make_parser_set(titles), users) + syncer._sync_users(make_parser_set(titles), users, is_overwrite=is_overwrite) for k, v in expected.items(): assert Profile.objects.get(category_id=syncer.category_id, username=k).email == v From 1f5bec14a7f17d75edacb8365207945b225e30f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E4=BD=A9=E7=8F=8A=5Bshiisa=5D?= Date: Tue, 6 Dec 2022 14:54:48 +0800 Subject: [PATCH 12/48] fix:lint check #issue728 --- src/api/bkuser_core/categories/plugins/local/syncer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/bkuser_core/categories/plugins/local/syncer.py b/src/api/bkuser_core/categories/plugins/local/syncer.py index f4d7e3b92..9b272805d 100644 --- a/src/api/bkuser_core/categories/plugins/local/syncer.py +++ b/src/api/bkuser_core/categories/plugins/local/syncer.py @@ -183,7 +183,7 @@ def _department_profile_relation_handle( old_department_profile_relations = DepartmentThroughModel.objects.filter(profile_id=profile_id) # Note: 有新关系可能存在重复数据,所以这里使用不变的old_department_set用于后续判断是否存在的依据, # 而不使用后面会变更的old_department_relations数据 - old_department_set = set([r.department_id for r in old_department_profile_relations]) + old_department_set = {r.department_id for r in old_department_profile_relations} old_department_relations = {r.department_id: r.id for r in old_department_profile_relations} for department in cell_parser.parse_to_db_obj(department_groups): From 4ba9f2f740ebc93d27a4930efaf021432037b91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E4=BD=A9=E7=8F=8A=5Bshiisa=5D?= Date: Tue, 6 Dec 2022 17:10:22 +0800 Subject: [PATCH 13/48] =?UTF-8?q?feat:=20=E7=99=BB=E5=BD=95=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E8=AF=95=E9=94=99=E9=94=81=E5=AE=9A=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E5=AF=B9admin=E7=94=A8=E6=88=B7=E8=B1=81?= =?UTF-8?q?=E5=85=8D=20#810?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bkuser_core/api/login/views.py | 4 +-- .../tests/apis/v2/profiles/test_login.py | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/api/bkuser_core/api/login/views.py b/src/api/bkuser_core/api/login/views.py index b4d2526b5..98a64288d 100644 --- a/src/api/bkuser_core/api/login/views.py +++ b/src/api/bkuser_core/api/login/views.py @@ -102,8 +102,8 @@ def login(self, request): time_aware_now = now() config_loader = ConfigProvider(category_id=category.id) - # Admin 用户只需直接判断 密码是否正确 (只有本地目录有密码配置) - if not profile.is_superuser and category.type in [CategoryType.LOCAL.value]: + # 由于安全检测等原因,取消原先对admin用户的检查豁免 + if category.type in [CategoryType.LOCAL.value]: # 判断账户状态 if profile.status in [ diff --git a/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py b/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py index 01bb5ce48..6dc9e930c 100644 --- a/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py +++ b/src/api/bkuser_core/tests/apis/v2/profiles/test_login.py @@ -19,7 +19,7 @@ from bkuser_core.categories.constants import CategoryStatus from bkuser_core.profiles.constants import ProfileStatus, RoleCodeEnum from bkuser_core.tests.apis.utils import get_api_factory -from bkuser_core.tests.utils import make_simple_category, make_simple_profile +from bkuser_core.tests.utils import get_one_object, make_simple_category, make_simple_profile from bkuser_core.user_settings.models import Setting pytestmark = pytest.mark.django_db @@ -226,7 +226,7 @@ def test_check_error(self, factory, check_view): response = check_view(request=request) assert response.data["code"] == "PASSWORD_ERROR" - # 超级用户不判断用户状态 + # 超级用户不对用户状态判断做豁免 p.role = RoleCodeEnum.SUPERUSER.value p.save() request = factory.post( @@ -234,8 +234,7 @@ def test_check_error(self, factory, check_view): data={"username": "logintest", "password": "testpwd", "domain": "testdomain"}, ) response = check_view(request=request) - assert response.data - self._assert_required_keys_exist(response.data) + assert response.data["code"] == "PASSWORD_ERROR" p.role = RoleCodeEnum.STAFF.value p.save() @@ -298,6 +297,24 @@ def test_check_auto_lock(self, factory, check_view): self._assert_required_keys_exist(response.data) + # admin用户不做豁免: + admin = get_one_object("profile", username="admin") + admin.set_password("adminpwd") + + request = factory.post("/api/v1/login/check/", data={"username": "admin", "password": "wrongpwd"}) + response = check_view(request=request) + assert response.data["code"] == "PASSWORD_ERROR" + + request = factory.post("/api/v1/login/check/", data={"username": "admin", "password": "wrongpwd"}) + response = check_view(request=request) + assert response.data["code"] == "PASSWORD_ERROR" + + # 确保解锁了 + time.sleep(2) + request = factory.post("/api/v1/login/check/", data={"username": "admin", "password": "adminpwd"}) + response = check_view(request=request) + self._assert_required_keys_exist(response.data) + def test_batch_query(self): """测试批量查询""" # request = self.factory.post('/api/v2/profiles/') From 481626ef8585ce1aa44975c5e0a60b9bc5fdbf24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E4=BD=A9=E7=8F=8A=5Bshiisa=5D?= Date: Wed, 7 Dec 2022 13:59:33 +0800 Subject: [PATCH 14/48] fix: mypy check #728 --- src/api/bkuser_core/categories/plugins/local/syncer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/bkuser_core/categories/plugins/local/syncer.py b/src/api/bkuser_core/categories/plugins/local/syncer.py index 9b272805d..31925cf48 100644 --- a/src/api/bkuser_core/categories/plugins/local/syncer.py +++ b/src/api/bkuser_core/categories/plugins/local/syncer.py @@ -176,7 +176,11 @@ def _judge_data_all_none(raw_data: list) -> bool: return all(x is None for x in raw_data) def _department_profile_relation_handle( - self, is_overwrite, department_groups, profile_id, should_deleted_department_profile_relation_ids + self, + is_overwrite: bool, + department_groups: str, + profile_id: int, + should_deleted_department_profile_relation_ids: list, ): cell_parser = DepartmentCellParser(self.category_id) # 已存在的用户-部门关系 From bcb724b19c163beef4540d254e773c7eedf727df Mon Sep 17 00:00:00 2001 From: neronkl <49228807+neronkl@users.noreply.github.com> Date: Wed, 7 Dec 2022 14:40:47 +0800 Subject: [PATCH 15/48] =?UTF-8?q?feature:=20=E9=A1=B5=E9=9D=A2=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E5=AF=86=E7=A0=81=E9=87=8D=E7=BD=AE=EF=BC=8Crsa?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=20(#818)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: 页面操作密码重置,rsa加密 --- src/api/bkuser_core/api/web/category/views.py | 1 - src/api/bkuser_core/api/web/constants.py | 12 ++ .../api/web/password/serializers.py | 12 ++ src/api/bkuser_core/api/web/password/urls.py | 5 + src/api/bkuser_core/api/web/password/views.py | 50 +++++-- .../api/web/profile/serializers.py | 6 +- src/api/bkuser_core/api/web/utils.py | 30 +++- .../commands/enable_pwd_rsa_encrypt.py | 140 ++++++++++++++++++ .../0019_alter_local_password_rsa_config.py | 55 +++++++ src/api/poetry.lock | 27 +++- src/api/pyproject.toml | 2 + 11 files changed, 324 insertions(+), 16 deletions(-) create mode 100644 src/api/bkuser_core/api/web/constants.py create mode 100644 src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py create mode 100644 src/api/bkuser_core/user_settings/migrations/0019_alter_local_password_rsa_config.py diff --git a/src/api/bkuser_core/api/web/category/views.py b/src/api/bkuser_core/api/web/category/views.py index 7f1a04eb0..368128d6f 100644 --- a/src/api/bkuser_core/api/web/category/views.py +++ b/src/api/bkuser_core/api/web/category/views.py @@ -117,7 +117,6 @@ def get_queryset(self): category = get_category(category_id) namespace = self.kwargs["namespace"] metas = list_setting_metas(category.type, None, namespace) - return Setting.objects.filter(meta__in=metas, category_id=category_id) def post(self, request, *args, **kwargs): diff --git a/src/api/bkuser_core/api/web/constants.py b/src/api/bkuser_core/api/web/constants.py new file mode 100644 index 000000000..38ff6fbe8 --- /dev/null +++ b/src/api/bkuser_core/api/web/constants.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" + +EXCLUDE_SETTINGS_META_KEYS = ["password_rsa_private_key"] diff --git a/src/api/bkuser_core/api/web/password/serializers.py b/src/api/bkuser_core/api/web/password/serializers.py index 377565602..cb1549d34 100644 --- a/src/api/bkuser_core/api/web/password/serializers.py +++ b/src/api/bkuser_core/api/web/password/serializers.py @@ -12,6 +12,7 @@ from rest_framework import serializers from bkuser_core.api.web.serializers import Base64OrPlainField +from bkuser_core.api.web.utils import get_raw_password, get_token_handler class PasswordResetSendEmailInputSLZ(serializers.Serializer): @@ -22,7 +23,18 @@ class PasswordResetByTokenInputSLZ(serializers.Serializer): token = serializers.CharField(required=True, max_length=254) password = Base64OrPlainField(required=True, max_length=254) + def validate(self, attrs): + token_holder = get_token_handler(token=attrs["token"]) + profile = token_holder.profile + # 对于密码输入可能是明文也可能是密文,根据配置自动判断解析出明文(密文只是与前端加密传递,与后续逻辑无关) + attrs["password"] = get_raw_password(profile.category_id, attrs["password"]) + return attrs + class PasswordModifyInputSLZ(serializers.Serializer): old_password = Base64OrPlainField(required=True, max_length=254) new_password = Base64OrPlainField(required=True, max_length=254) + + +class PasswordListSettingsByTokenInputSLZ(serializers.Serializer): + token = serializers.CharField(required=True, max_length=254) diff --git a/src/api/bkuser_core/api/web/password/urls.py b/src/api/bkuser_core/api/web/password/urls.py index 92538a731..013e98ace 100644 --- a/src/api/bkuser_core/api/web/password/urls.py +++ b/src/api/bkuser_core/api/web/password/urls.py @@ -29,4 +29,9 @@ views.PasswordModifyApi.as_view(), name="password.modify", ), + path( + "settings/by_token/", + views.PasswordListSettingsByTokenApi.as_view(), + name="password.get_settings.by_token", + ), ] diff --git a/src/api/bkuser_core/api/web/password/views.py b/src/api/bkuser_core/api/web/password/views.py index 6fde3084d..473ede519 100644 --- a/src/api/bkuser_core/api/web/password/views.py +++ b/src/api/bkuser_core/api/web/password/views.py @@ -16,8 +16,20 @@ from rest_framework import generics, status from rest_framework.response import Response -from .serializers import PasswordModifyInputSLZ, PasswordResetByTokenInputSLZ, PasswordResetSendEmailInputSLZ -from bkuser_core.api.web.utils import get_operator, validate_password +from bkuser_core.api.web.category.serializers import CategorySettingOutputSLZ +from bkuser_core.api.web.password.serializers import ( + PasswordListSettingsByTokenInputSLZ, + PasswordModifyInputSLZ, + PasswordResetByTokenInputSLZ, + PasswordResetSendEmailInputSLZ, +) +from bkuser_core.api.web.utils import ( + get_category, + get_operator, + get_token_handler, + list_setting_metas, + validate_password, +) from bkuser_core.audit.constants import OperationType from bkuser_core.audit.utils import create_general_log from bkuser_core.categories.models import ProfileCategory @@ -27,6 +39,8 @@ from bkuser_core.profiles.signals import post_profile_update from bkuser_core.profiles.tasks import send_password_by_email from bkuser_core.profiles.utils import parse_username_domain +from bkuser_core.user_settings.constants import SettingsEnableNamespaces +from bkuser_core.user_settings.models import Setting logger = logging.getLogger(__name__) @@ -72,16 +86,9 @@ def post(self, request, *args, **kwargs): token = data["token"] pending_password = data["password"] - try: - token_holder = ProfileTokenHolder.objects.get(token=token, enabled=True) - except ProfileTokenHolder.DoesNotExist: - logger.info("token<%s> not exist in db", token) - raise error_codes.CANNOT_GET_TOKEN_HOLDER - - if token_holder.expired: - raise error_codes.PROFILE_TOKEN_EXPIRED - + token_holder = get_token_handler(token) profile = token_holder.profile + validate_password(profile, pending_password) profile.password = make_password(pending_password) profile.password_update_time = now() @@ -143,3 +150,24 @@ def post(self, request, *args, **kwargs): extra_values=modify_summary, ) return Response(status=status.HTTP_200_OK) + + +class PasswordListSettingsByTokenApi(generics.ListAPIView): + serializer_class = CategorySettingOutputSLZ + + def get(self, request, *args, **kwargs): + slz = PasswordListSettingsByTokenInputSLZ(data=request.query_params) + slz.is_valid(raise_exception=True) + + data = slz.validated_data + token = data["token"] + + token_holder = get_token_handler(token) + profile = token_holder.profile + + category = get_category(profile.category_id) + namespace = SettingsEnableNamespaces.PASSWORD.value + metas = list_setting_metas(category.type, None, namespace) + settings = Setting.objects.filter(meta__in=metas, category_id=profile.category_id) + + return Response(self.serializer_class(settings, many=True).data) diff --git a/src/api/bkuser_core/api/web/profile/serializers.py b/src/api/bkuser_core/api/web/profile/serializers.py index 08cbde9ef..7c75feae5 100644 --- a/src/api/bkuser_core/api/web/profile/serializers.py +++ b/src/api/bkuser_core/api/web/profile/serializers.py @@ -13,7 +13,7 @@ from rest_framework import serializers from bkuser_core.api.web.serializers import StringArrayField -from bkuser_core.api.web.utils import get_default_category_id +from bkuser_core.api.web.utils import get_default_category_id, get_raw_password from bkuser_core.profiles.models import Profile from bkuser_core.profiles.validators import validate_username @@ -105,12 +105,16 @@ class ProfileSearchOutputSLZ(serializers.Serializer): class ProfileUpdateInputSLZ(serializers.ModelSerializer): leader = serializers.ListField(child=serializers.IntegerField(), required=False) departments = serializers.ListField(child=serializers.IntegerField(), required=False) + password = serializers.CharField(required=False, write_only=True) class Meta: model = Profile # NOTE: 相对原来的api区别, 不支持extras/create_time/update_time更新 exclude = ["category_id", "username", "domain", "extras", "create_time", "update_time"] + def validate_password(self, password): + return get_raw_password(self.instance.category_id, password) + class ProfileCreateInputSLZ(serializers.ModelSerializer): category_id = serializers.IntegerField(required=False) diff --git a/src/api/bkuser_core/api/web/utils.py b/src/api/bkuser_core/api/web/utils.py index 6a3ca18c2..4829343d8 100644 --- a/src/api/bkuser_core/api/web/utils.py +++ b/src/api/bkuser_core/api/web/utils.py @@ -8,21 +8,24 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +import base64 import logging from typing import Dict, Union from django.conf import settings +from bkuser_core.api.web.constants import EXCLUDE_SETTINGS_META_KEYS from bkuser_core.categories.models import ProfileCategory from bkuser_core.common.error_codes import error_codes from bkuser_core.departments.models import Department from bkuser_core.profiles.cache import get_extras_default_from_local_cache -from bkuser_core.profiles.models import DynamicFieldInfo, Profile +from bkuser_core.profiles.models import DynamicFieldInfo, Profile, ProfileTokenHolder from bkuser_core.profiles.password import PasswordValidator from bkuser_core.profiles.utils import check_former_passwords from bkuser_core.user_settings.exceptions import SettingHasBeenDisabledError from bkuser_core.user_settings.loader import ConfigProvider from bkuser_core.user_settings.models import SettingMeta +from bkuser_global.crypt import rsa_decrypt_password logger = logging.getLogger(__name__) @@ -82,7 +85,7 @@ def list_setting_metas(category_type: str, region: str, namespace: str) -> list: """ List setting metas. """ - queryset = SettingMeta.objects.filter(category_type=category_type) + queryset = SettingMeta.objects.filter(category_type=category_type).exclude(key__in=EXCLUDE_SETTINGS_META_KEYS) if region: queryset = queryset.filter(region=region) if namespace: @@ -152,3 +155,26 @@ def get_extras_with_default_values(extras_from_db: Union[dict, list]) -> dict: extras.update(formatted_extras) return extras + + +def get_raw_password(category_id: int, encrypted_password: str) -> str: + config_loader = ConfigProvider(category_id=category_id) + enable_rsa_encrypted = config_loader.get("enable_password_rsa_encrypted") + # 未开启,或者未配置rsa + if not enable_rsa_encrypted: + return encrypted_password + rsa_private_key = base64.b64decode(config_loader["password_rsa_private_key"]).decode() + return rsa_decrypt_password(encrypted_password, rsa_private_key) + + +def get_token_handler(token: str) -> ProfileTokenHolder: + try: + token_holder = ProfileTokenHolder.objects.get(token=token, enabled=True) + except ProfileTokenHolder.DoesNotExist: + logger.info("token<%s> not exist in db", token) + raise error_codes.CANNOT_GET_TOKEN_HOLDER + + if token_holder.expired: + raise error_codes.PROFILE_TOKEN_EXPIRED + + return token_holder diff --git a/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py b/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py new file mode 100644 index 000000000..1c9fd4018 --- /dev/null +++ b/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import base64 +import logging +import traceback + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa as crypto_rsa +from django.core.management.base import BaseCommand +from django.db import transaction + +from bkuser_core.categories.constants import CategoryType +from bkuser_core.categories.models import ProfileCategory +from bkuser_core.user_settings.models import Setting, SettingMeta + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "enable category rsa" + + def add_arguments(self, parser): + parser.add_argument("--category_id", type=str, help="目录ID", required=True) + parser.add_argument("--random_flag", type=bool, default=True, help="是否随机生成") + parser.add_argument("--key_length", type=int, default=1024, help="随机密钥对的长度") + parser.add_argument("--private_key_file", type=str, default="", help="rsa私钥pem文件目录") + parser.add_argument("--public_key_file", type=str, default="", help="rsa公钥pem文件目录") + + def validate_rsa_secret(self, private_key_content: bytes, public_key_content: bytes) -> bool: + testing_msg = "Hello World !!!" + + private_key = serialization.load_pem_private_key(private_key_content, password=None, backend=default_backend()) + public_key = serialization.load_pem_public_key(public_key_content, backend=default_backend()) + + common_condition = padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None + ) + + # 先公钥加密 + encrypt_msg = public_key.encrypt(testing_msg.encode(), common_condition) + + # 解密 + decrypt_msg = private_key.decrypt(encrypt_msg, common_condition) + result_msg = decrypt_msg.decode() + + if result_msg != testing_msg: + return False + return True + + def create_rsa_secret(self, options: dict): + random_flag = options.get("random_flag") + if not random_flag: + # read the private_key and public key from the file + private_key_file = options.get("private_key_file") + public_key_file = options.get("public_key_file") + with open(private_key_file, "rb") as private_file: + private_key = private_file.read() + + with open(public_key_file, "rb") as public_file: + public_key = public_file.read() + + if not self.validate_rsa_secret(private_key, private_key): + self.stdout.write("These pem files do not matching") + raise Exception + else: + self.stdout.write("Private key and public key are creating randomly") + key_length = options.get("key_length") + # 随机生成rsa 秘钥对 + private_key_origin = crypto_rsa.generate_private_key( + public_exponent=65537, key_size=key_length, backend=default_backend() + ) + public_key_origin = private_key_origin.public_key() + + private_key = private_key_origin.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + + public_key = public_key_origin.public_bytes( + encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.PKCS1 + ) + + # base64加密入库 + public_key = base64.b64encode(public_key).decode() + private_key = base64.b64encode(private_key).decode() + + return public_key, private_key + + def handle(self, *args, **options): + category_id = options.get("category_id") + self.stdout.write(f"enable category rsa: category_id={str(category_id)}") + + try: + public_key, private_key = self.create_rsa_secret(options) + category = ProfileCategory.objects.get(id=category_id) + if category.type != CategoryType.LOCAL.value: + self.stdout.write("Rsa setting only support the local category, please check your input") + return + + rsa_settings_filters = { + "enable_password_rsa_encrypted": True, + "password_rsa_private_key": private_key, + "password_rsa_public_key": public_key, + } + + meta_combo = {} + for key, value in rsa_settings_filters.items(): + meta = SettingMeta.objects.get(key=key) + meta_combo[meta] = value + + # 新增或更新该目录的user_setting设置:rsa配置 + with transaction.atomic(): + rsa_settings = [] + for meta, value in meta_combo.items(): + instance, _ = Setting.objects.get_or_create(meta=meta, category_id=category.id) + instance.value = value + rsa_settings.append(instance) + Setting.objects.bulk_update(rsa_settings, ["value"]) + + self.stdout.write(f"Category {category_id} Enable rsa successfully") + + except ProfileCategory.DoesNotExist: + self.stdout.write(f"Category is not exist( category_id={category_id} ), please check your input.") + return + + except Exception as e: + self.stdout.write(traceback.format_exc()) + self.stdout.write(f"Enable rsa failed: {e}") + return diff --git a/src/api/bkuser_core/user_settings/migrations/0019_alter_local_password_rsa_config.py b/src/api/bkuser_core/user_settings/migrations/0019_alter_local_password_rsa_config.py new file mode 100644 index 000000000..b52cabcf1 --- /dev/null +++ b/src/api/bkuser_core/user_settings/migrations/0019_alter_local_password_rsa_config.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from __future__ import unicode_literals + +from bkuser_core.categories.constants import CategoryType +from bkuser_core.user_settings.constants import SettingsEnableNamespaces +from django.db import migrations + + +def forwards_func(apps, schema_editor): + """更新默认目录 密码-rsa加密模板配置""" + SettingMeta = apps.get_model("user_settings", "SettingMeta") + + local_password_rsa_settings = [ + dict( + key="enable_password_rsa_encrypted", + default=False, + example=False + ), + dict( + key="password_rsa_private_key", + default="", + example="LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpkZnNkZmRzZgotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" + ), + dict( + key="password_rsa_public_key", + default="", + example="LS0tLS1CRUdJTiBSU0EgUFVCTElDS0VZLS0tLS0KZXJ0ZXJ0cmV0Ci0tLS0tRU5EIFJTQSBQVUJMSUNLRVktLS0tLQo=" + ) + ] + + for x in local_password_rsa_settings: + + meta, _ = SettingMeta.objects.get_or_create( + namespace=SettingsEnableNamespaces.PASSWORD.value, + category_type=CategoryType.LOCAL.value, + required=False, + **x + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("user_settings", "0018_alter_local_password_mail_config"), + ] + + operations = [migrations.RunPython(forwards_func)] diff --git a/src/api/poetry.lock b/src/api/poetry.lock index 6d4fa9220..a8ad2ac3c 100644 --- a/src/api/poetry.lock +++ b/src/api/poetry.lock @@ -1888,6 +1888,11 @@ type = "legacy" url = "https://mirrors.tencent.com/pypi/simple" reference = "tencent-mirrors" +[package.source] +type = "legacy" +url = "https://mirrors.tencent.com/pypi/simple" +reference = "tencent-mirrors" + [[package]] name = "pydantic" version = "1.9.0" @@ -2196,6 +2201,22 @@ type = "legacy" url = "https://mirrors.tencent.com/pypi/simple" reference = "tencent-mirrors" +[[package]] +name = "rsa" +version = "3.4.2" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[package.source] +type = "legacy" +url = "https://mirrors.tencent.com/pypi/simple" +reference = "tencent-mirrors" + [[package]] name = "ruamel.yaml" version = "0.17.21" @@ -2664,7 +2685,7 @@ reference = "tencent-mirrors" [metadata] lock-version = "1.1" python-versions = "3.6.14" -content-hash = "a5df43ed5650d6e3aa47d75a0ec94bea78d86ecf964646bb92734b3fa84308e9" +content-hash = "c86527e46c8c3188a858fa0e9160ec24c9fa1d3a31fbf6eae8a3e0c3eff6ccfb" [metadata.files] aenum = [ @@ -3642,6 +3663,10 @@ requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] +rsa = [ + {file = "rsa-3.4.2-py2.py3-none-any.whl", hash = "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd"}, + {file = "rsa-3.4.2.tar.gz", hash = "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5"}, +] "ruamel.yaml" = [ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, diff --git a/src/api/pyproject.toml b/src/api/pyproject.toml index b48791d0b..2fa406f96 100644 --- a/src/api/pyproject.toml +++ b/src/api/pyproject.toml @@ -55,6 +55,8 @@ opentelemetry-instrumentation-requests = "0.26b1" opentelemetry-instrumentation-celery = "0.26b1" opentelemetry-instrumentation-logging = "0.26b1" opentelemetry-exporter-jaeger = "1.7.1" +rsa = "3.4.2" + [tool.poetry.dev-dependencies] ipython = "^7.15.0" From 324d10ad804c39a8cfc2c3d34710618c411de2cc Mon Sep 17 00:00:00 2001 From: wklken Date: Wed, 7 Dec 2022 14:57:47 +0800 Subject: [PATCH 16/48] ci(github_action): enable check for pre_* --- .github/workflows/eslint.yaml | 4 ++-- .github/workflows/python-ci.yml | 4 ++-- .github/workflows/unittest.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/eslint.yaml b/.github/workflows/eslint.yaml index 7d7741a5d..35e0c901d 100644 --- a/.github/workflows/eslint.yaml +++ b/.github/workflows/eslint.yaml @@ -2,9 +2,9 @@ name: ESLint on: push: - branches: [ master, develop ] + branches: [ master, develop, pre_* ] pull_request: - branches: [ master, develop ] + branches: [ master, develop, pre_* ] jobs: build: diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index f5587818e..6daa67dfa 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -2,9 +2,9 @@ name: Python CI Check on: push: - branches: [ master, develop ] + branches: [ master, develop, pre_* ] pull_request: - branches: [ master, develop ] + branches: [ master, develop, pre_* ] jobs: build: diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index d9f73e751..9d7edd51e 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -2,9 +2,9 @@ name: Unittest on: push: - branches: [ master, develop ] + branches: [ master, develop, pre_* ] pull_request: - branches: [ master, develop ] + branches: [ master, develop, pre_* ] jobs: build: From ccc14775dd7449c01d0c1db8eb598ed0c6ebc010 Mon Sep 17 00:00:00 2001 From: neronkl <49228807+neronkl@users.noreply.github.com> Date: Wed, 7 Dec 2022 16:22:20 +0800 Subject: [PATCH 17/48] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=B1?= =?UTF-8?q?=E6=95=88toml=E6=96=87=E4=BB=B6=EF=BC=9Bmypy=20check=20failed?= =?UTF-8?q?=20(#830)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: mypy check failed --- .../commands/enable_pwd_rsa_encrypt.py | 12 ++++--- .../categories/plugins/local/syncer.py | 2 +- src/api/poetry.lock | 33 +++++++++++-------- src/api/pyproject.toml | 5 ++- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py b/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py index 1c9fd4018..f107c1821 100644 --- a/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py +++ b/src/api/bkuser_core/categories/management/commands/enable_pwd_rsa_encrypt.py @@ -59,10 +59,12 @@ def validate_rsa_secret(self, private_key_content: bytes, public_key_content: by def create_rsa_secret(self, options: dict): random_flag = options.get("random_flag") + private_key: bytes + public_key: bytes if not random_flag: # read the private_key and public key from the file - private_key_file = options.get("private_key_file") - public_key_file = options.get("public_key_file") + private_key_file: str = options.get("private_key_file", "") + public_key_file: str = options.get("public_key_file", "") with open(private_key_file, "rb") as private_file: private_key = private_file.read() @@ -92,10 +94,10 @@ def create_rsa_secret(self, options: dict): ) # base64加密入库 - public_key = base64.b64encode(public_key).decode() - private_key = base64.b64encode(private_key).decode() + public_key_base64: str = base64.b64encode(public_key).decode() + private_key_base64: str = base64.b64encode(private_key).decode() - return public_key, private_key + return public_key_base64, private_key_base64 def handle(self, *args, **options): category_id = options.get("category_id") diff --git a/src/api/bkuser_core/categories/plugins/local/syncer.py b/src/api/bkuser_core/categories/plugins/local/syncer.py index 31925cf48..eb345d449 100644 --- a/src/api/bkuser_core/categories/plugins/local/syncer.py +++ b/src/api/bkuser_core/categories/plugins/local/syncer.py @@ -216,7 +216,7 @@ def _sync_users(self, parser_set: "ParserSet", users: list, is_overwrite: bool = success_count = 0 total = len(users) - should_deleted_department_profile_relation_ids = [] + should_deleted_department_profile_relation_ids: list = [] for index, user_raw_info in enumerate(users): if self._judge_data_all_none(user_raw_info): logger.debug("empty line, skipping") diff --git a/src/api/poetry.lock b/src/api/poetry.lock index a8ad2ac3c..b01bbebf2 100644 --- a/src/api/poetry.lock +++ b/src/api/poetry.lock @@ -1888,11 +1888,6 @@ type = "legacy" url = "https://mirrors.tencent.com/pypi/simple" reference = "tencent-mirrors" -[package.source] -type = "legacy" -url = "https://mirrors.tencent.com/pypi/simple" -reference = "tencent-mirrors" - [[package]] name = "pydantic" version = "1.9.0" @@ -2203,11 +2198,11 @@ reference = "tencent-mirrors" [[package]] name = "rsa" -version = "3.4.2" +version = "4.9" description = "Pure-Python RSA implementation" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6,<4" [package.dependencies] pyasn1 = ">=0.1.3" @@ -2685,7 +2680,7 @@ reference = "tencent-mirrors" [metadata] lock-version = "1.1" python-versions = "3.6.14" -content-hash = "c86527e46c8c3188a858fa0e9160ec24c9fa1d3a31fbf6eae8a3e0c3eff6ccfb" +content-hash = "00055fa21fd0bbd9fe0077141d7901e5c9505b59c69cb6eb49072e3927fe6be4" [metadata.files] aenum = [ @@ -2702,7 +2697,7 @@ amqp = [ {file = "amqp-2.6.1.tar.gz", hash = "sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21"}, ] apigw-manager = [ - {file = "apigw_manager-1.1.5-py3-none-any.whl", hash = "md5:cc2df9c0dc88460737b338213b7cc511"}, + {file = "apigw_manager-1.1.5-py3-none-any.whl", hash = "sha256:694ef800ace5344d0b2b5a88dbcd51c143fd3597066cab508b3141cc2f3449d2"}, ] appnope = [ {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, @@ -2737,10 +2732,10 @@ bk-iam = [ {file = "bk_iam-1.1.10-py2.py3-none-any.whl", hash = "sha256:83b01016c54e77b39ad474d5265686117bf4b9a70d120397e9c4ae493ce6e8ee"}, ] bkapi-bk-apigateway = [ - {file = "bkapi-bk-apigateway-1.0.6.tar.gz", hash = "md5:a29cfd774a9185c920a394cdec0a9493"}, + {file = "bkapi-bk-apigateway-1.0.6.tar.gz", hash = "sha256:561bca6fcdb84f7cc9e7b3aee2b70318859b4bd8d374995df68f7dc394ea8a5d"}, ] bkapi-client-core = [ - {file = "bkapi_client_core-1.1.5-py2.py3-none-any.whl", hash = "md5:30099edee4570333fe103e71fe8735f5"}, + {file = "bkapi_client_core-1.1.5-py2.py3-none-any.whl", hash = "sha256:780d35d6d1804851e5fcd8c4a3fc785cee9b760d6209ca61a1bafe20de13436b"}, ] black = [ {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, @@ -3605,6 +3600,13 @@ pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, @@ -3664,8 +3666,8 @@ requests = [ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rsa = [ - {file = "rsa-3.4.2-py2.py3-none-any.whl", hash = "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd"}, - {file = "rsa-3.4.2.tar.gz", hash = "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5"}, + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, ] "ruamel.yaml" = [ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, @@ -3673,6 +3675,7 @@ rsa = [ ] "ruamel.yaml.clib" = [ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:066f886bc90cc2ce44df8b5f7acfc6a7e2b2e672713f027136464492b0c34d7c"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, @@ -3682,18 +3685,22 @@ rsa = [ {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d3c620a54748a3d4cf0bcfe623e388407c8e85a4b06b8188e126302bcab93ea8"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:210c8fcfeff90514b7133010bf14e3bad652c8efde6b20e00c43854bf94fa5a6"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:61bc5e5ca632d95925907c569daa559ea194a4d16084ba86084be98ab1cec1c6"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1b4139a6ffbca8ef60fdaf9b33dec05143ba746a6f0ae0f9d11d38239211d335"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, diff --git a/src/api/pyproject.toml b/src/api/pyproject.toml index 2fa406f96..bbecc495b 100644 --- a/src/api/pyproject.toml +++ b/src/api/pyproject.toml @@ -55,8 +55,7 @@ opentelemetry-instrumentation-requests = "0.26b1" opentelemetry-instrumentation-celery = "0.26b1" opentelemetry-instrumentation-logging = "0.26b1" opentelemetry-exporter-jaeger = "1.7.1" -rsa = "3.4.2" - +rsa = "^4.9" [tool.poetry.dev-dependencies] ipython = "^7.15.0" @@ -88,4 +87,4 @@ url = "https://mirrors.tencent.com/pypi/simple/" [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "bkuser_core.config.overlays.dev" -addopts = "--disable-pytest-warnings --reuse-db" +addopts = "--disable-pytest-warnings --reuse-db" \ No newline at end of file From 0287c71c93e663249a762463fc2724b5be425e6e Mon Sep 17 00:00:00 2001 From: Canway-shiisa <90179140+Canway-shiisa@users.noreply.github.com> Date: Fri, 9 Dec 2022 20:59:34 +0800 Subject: [PATCH 18/48] =?UTF-8?q?feat:=20=E5=AF=BC=E5=87=BA=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0<=E5=85=A8=E5=90=8D>=20#618=20(#835)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 导出增加<全名> #618 --- src/api/bkuser_core/api/web/audit/serializers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api/bkuser_core/api/web/audit/serializers.py b/src/api/bkuser_core/api/web/audit/serializers.py index 5b91a922c..5cbb0a505 100644 --- a/src/api/bkuser_core/api/web/audit/serializers.py +++ b/src/api/bkuser_core/api/web/audit/serializers.py @@ -15,6 +15,7 @@ from rest_framework import serializers from .constants import LOGIN_FAILED_REASON_MAP, OPERATION_ABOUT_PASSWORD, OPERATION_NAME_MAP, OPERATION_OBJ_NAME_MAP +from bkuser_core.profiles.models import Profile PLACE_HOLDER = "--" @@ -35,6 +36,7 @@ class GeneralLogListInputSLZ(LogListInputSLZ): class GeneralLogOutputSLZ(serializers.Serializer): id = serializers.IntegerField(help_text=_("ID")) extra_value = serializers.JSONField(help_text=_("额外信息")) + display_name = serializers.CharField(help_text=_("用户全名"), read_only=True) operator = serializers.CharField(help_text=_("操作者")) create_time = serializers.DateTimeField(help_text=_("创建时间")) status = serializers.CharField(help_text=_("状态")) @@ -57,12 +59,15 @@ def to_representation(self, obj): category_id = extra_value.get("category_id") category_display_name = category_name_map.get(category_id, PLACE_HOLDER) + operator_profile = Profile.objects.filter(username=obj.operator).first() + display_name = operator_profile.display_name if operator_profile else "" return { "datetime": datetime.datetime.strptime(instance["create_time"], "%Y-%m-%dT%H:%M:%S.%fZ"), "operator": instance["operator"], "target_obj": instance["target_obj"], "category_display_name": category_display_name, + "display_name": display_name, "operation": instance["operation"], "client_ip": extra_value.get("client_ip", PLACE_HOLDER), } @@ -79,7 +84,7 @@ class LoginLogOutputSLZ(serializers.Serializer): # datetime = serializers.CharField(source="create_time", help_text=_("登录时间"), required=False) is_success = serializers.BooleanField(help_text=_("是否登录成功"), required=False) username = serializers.CharField(help_text=_("登录用户"), source="profile.username") - + display_name = serializers.CharField(help_text=_("用户全名"), source="profile.display_name") datetime = serializers.SerializerMethodField(help_text=_("登录时间"), required=False) category_display_name = serializers.SerializerMethodField(help_text=_("所属目录"), required=False) client_ip = serializers.SerializerMethodField(help_text=_("客户端 IP"), required=False) From ffaa738c91f1a104ecffcfbcd101aa615a0370bf Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Mon, 12 Dec 2022 10:23:43 +0800 Subject: [PATCH 19/48] =?UTF-8?q?fix:=20xss=E6=A0=A1=E9=AA=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/src/plugins/methods.js | 5 +++++ src/pages/src/views/organization/details/InputString.vue | 2 +- src/pages/src/views/organization/details/UserMaterial.vue | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/src/plugins/methods.js b/src/pages/src/plugins/methods.js index 3c0155ae7..8f4fb0092 100644 --- a/src/pages/src/plugins/methods.js +++ b/src/pages/src/plugins/methods.js @@ -261,6 +261,11 @@ const methods = { } return length; }; + + // 转换字符串尖括号 + Vue.prototype.$xssVerification = function (data) { + return data.replace(//g, '>'); + }; }, }; diff --git a/src/pages/src/views/organization/details/InputString.vue b/src/pages/src/views/organization/details/InputString.vue index 377beadfd..1f4b0a2bc 100644 --- a/src/pages/src/views/organization/details/InputString.vue +++ b/src/pages/src/views/organization/details/InputString.vue @@ -59,7 +59,7 @@ export default { methods: { // 失焦验证 verifyInput(item) { - item.value = item.value.replace(//g, '>'); + item.value = this.$xssVerification(item.value); if (!item.require) { return; } diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index 45152055c..6caf8e9c3 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -88,7 +88,7 @@

{{phoneNumber}}

-

{{fieldInfo.value || '--'}}

+

{{$xssVerification(fieldInfo.value) || '--'}}

From 4246028df6b6eae14c088443ead117d0fd3e05ee Mon Sep 17 00:00:00 2001 From: Canway-shiisa <90179140+Canway-shiisa@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:23:28 +0800 Subject: [PATCH 20/48] feat: check old password for admin #811 (#840) * feat: check old password for admin #811 --- .../api/web/profile/serializers.py | 4 ++ src/api/bkuser_core/api/web/profile/views.py | 13 +++- src/api/bkuser_core/audit/constants.py | 6 ++ src/api/bkuser_core/audit/handlers.py | 11 ++++ src/api/bkuser_core/audit/managers.py | 18 +++++- .../migrations/0007_auto_20221212_1131.py | 22 +++++++ src/api/bkuser_core/audit/models.py | 10 ++- src/api/bkuser_core/common/error_codes.py | 1 + src/api/bkuser_core/config/common/system.py | 8 +++ src/api/bkuser_core/profiles/models.py | 4 ++ src/api/bkuser_core/profiles/utils.py | 63 +++++++++++++++++-- 11 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 src/api/bkuser_core/audit/migrations/0007_auto_20221212_1131.py diff --git a/src/api/bkuser_core/api/web/profile/serializers.py b/src/api/bkuser_core/api/web/profile/serializers.py index 7c75feae5..729833469 100644 --- a/src/api/bkuser_core/api/web/profile/serializers.py +++ b/src/api/bkuser_core/api/web/profile/serializers.py @@ -106,6 +106,7 @@ class ProfileUpdateInputSLZ(serializers.ModelSerializer): leader = serializers.ListField(child=serializers.IntegerField(), required=False) departments = serializers.ListField(child=serializers.IntegerField(), required=False) password = serializers.CharField(required=False, write_only=True) + old_password = serializers.CharField(required=False, write_only=True) # 只有admin用户重置密码时才需要传递该字段 class Meta: model = Profile @@ -115,6 +116,9 @@ class Meta: def validate_password(self, password): return get_raw_password(self.instance.category_id, password) + def validate_old_password(self, old_password): + return get_raw_password(self.instance.category_id, old_password) + class ProfileCreateInputSLZ(serializers.ModelSerializer): category_id = serializers.IntegerField(required=False) diff --git a/src/api/bkuser_core/api/web/profile/views.py b/src/api/bkuser_core/api/web/profile/views.py index 679e93567..3f71ca5d3 100644 --- a/src/api/bkuser_core/api/web/profile/views.py +++ b/src/api/bkuser_core/api/web/profile/views.py @@ -40,7 +40,13 @@ from bkuser_core.profiles.exceptions import CountryISOCodeNotMatch from bkuser_core.profiles.models import DynamicFieldInfo, Profile from bkuser_core.profiles.signals import post_profile_create, post_profile_update -from bkuser_core.profiles.utils import align_country_iso_code, make_password_by_config, parse_username_domain +from bkuser_core.profiles.utils import ( + align_country_iso_code, + check_old_password, + make_password_by_config, + parse_username_domain, + should_check_old_password, +) from bkuser_core.user_settings.constants import SettingsEnableNamespaces from bkuser_core.user_settings.models import Setting, SettingMeta @@ -114,7 +120,6 @@ def _update(self, request, partial): slz = ProfileUpdateInputSLZ(instance, data=request.data, partial=partial) slz.is_valid(raise_exception=True) operate_type = OperationType.UPDATE.value - validated_data = slz.validated_data # 前端是把extras字段打平提交的 @@ -158,6 +163,10 @@ def _update(self, request, partial): update_summary = {"request": request} # 密码修改加密 if validated_data.get("password"): + # 如果重置的是admin账号的密码,需要对原始密码进行校验 + if should_check_old_password(username=instance.username): + check_old_password(instance=instance, old_password=validated_data["old_password"], request=request) + operate_type = ( OperationType.FORGET_PASSWORD.value if request.headers.get("User-From-Token") diff --git a/src/api/bkuser_core/audit/constants.py b/src/api/bkuser_core/audit/constants.py index 272c2dfe7..d9f40ecad 100644 --- a/src/api/bkuser_core/audit/constants.py +++ b/src/api/bkuser_core/audit/constants.py @@ -35,6 +35,12 @@ class LogInFailReason(AutoLowerEnum): ) +class ResetPasswordFailReason(AutoLowerEnum): + BAD_OLD_PASSWORD = auto() + + _choices_labels = ((BAD_OLD_PASSWORD, "原密码校验错误"),) + + class OperationType(AutoLowerEnum): CREATE = auto() UPDATE = auto() diff --git a/src/api/bkuser_core/audit/handlers.py b/src/api/bkuser_core/audit/handlers.py index 0792bcefb..5cdbb0c76 100644 --- a/src/api/bkuser_core/audit/handlers.py +++ b/src/api/bkuser_core/audit/handlers.py @@ -42,6 +42,17 @@ def create_reset_password_log(sender, instance: "Profile", operator: str, extra_ except Exception: # pylint: disable=broad-except logger.exception("failed to create reset password log") + if "failed_reason" in extra_values: + try: + create_profile_log( + instance, + "ResetPassword", + {"is_success": False, "reason": extra_values["failed_reason"]}, + extra_values["request"], + ) + except Exception: # pylint: disable=broad-except + logger.exception("failed to create reset password log") + @receiver([post_profile_create, post_department_create, post_category_create, post_field_create, post_setting_create]) def create_audit_log(sender, instance: "Profile", operator: str, extra_values: dict, **kwargs): diff --git a/src/api/bkuser_core/audit/managers.py b/src/api/bkuser_core/audit/managers.py index d12ee78fd..3852b47ac 100644 --- a/src/api/bkuser_core/audit/managers.py +++ b/src/api/bkuser_core/audit/managers.py @@ -16,12 +16,28 @@ from django.db import models from django.utils.timezone import now -from .constants import LogInFailReason +from .constants import LogInFailReason, ResetPasswordFailReason class ResetPasswordManager(models.Manager): """重置密码DB管理器""" + def latest_check_old_password_failed_count(self): + """最近一段时间重置密码失败的次数,其中最近一段时间指从上一次成功重置密码后到现在""" + # 查找最近一次成功的时间 + try: + latest_time = self.filter(is_success=True).latest().create_time + except ObjectDoesNotExist: + # 当没有任何成功记录时,防止存在大量失败记录时进行统计导致可能的慢查询,只计算默认配置的统计时间 + # 这里取配置里默认设置的统计时间 + latest_time = now() - datetime.timedelta(seconds=settings.RESET_PASSWORD_RECORD_COUNT_SECONDS) + + return self.filter( + is_success=False, + reason=ResetPasswordFailReason.BAD_OLD_PASSWORD.value, + create_time__gte=latest_time, + ).count() + class LogInManager(models.Manager): def latest_failed_count(self) -> int: diff --git a/src/api/bkuser_core/audit/migrations/0007_auto_20221212_1131.py b/src/api/bkuser_core/audit/migrations/0007_auto_20221212_1131.py new file mode 100644 index 000000000..c12529ad8 --- /dev/null +++ b/src/api/bkuser_core/audit/migrations/0007_auto_20221212_1131.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.13 on 2022-12-12 03:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audit', '0006_alter_login_index_together'), + ] + + operations = [ + migrations.AlterModelOptions( + name='resetpassword', + options={'get_latest_by': 'create_time', 'ordering': ['-create_time']}, + ), + migrations.AddField( + model_name='resetpassword', + name='reason', + field=models.CharField(blank=True, choices=[('bad_old_password', '原密码校验错误')], max_length=32, null=True, verbose_name='重置密码失败原因'), + ), + ] diff --git a/src/api/bkuser_core/audit/models.py b/src/api/bkuser_core/audit/models.py index c6bdfbd7d..0d8b2a76c 100644 --- a/src/api/bkuser_core/audit/models.py +++ b/src/api/bkuser_core/audit/models.py @@ -15,7 +15,7 @@ from django.db import models from jsonfield import JSONField -from bkuser_core.audit.constants import LogInFailReason, OperationStatus +from bkuser_core.audit.constants import LogInFailReason, OperationStatus, ResetPasswordFailReason from bkuser_core.audit.managers import LogInManager, ResetPasswordManager from bkuser_core.common.fields import EncryptField from bkuser_core.common.models import TimestampedModel @@ -102,6 +102,13 @@ class ResetPassword(ProfileRelatedLog): token = models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, null=True) is_success = models.BooleanField("是否重置成功", default=False) password = EncryptField(default="") + reason = models.CharField( + "重置密码失败原因", + max_length=32, + choices=ResetPasswordFailReason.get_choices(), + null=True, + blank=True, + ) objects = ResetPasswordManager() @@ -110,3 +117,4 @@ def __str__(self): class Meta: ordering = ["-create_time"] + get_latest_by = "create_time" diff --git a/src/api/bkuser_core/common/error_codes.py b/src/api/bkuser_core/common/error_codes.py index 666f525c1..e23d34e48 100644 --- a/src/api/bkuser_core/common/error_codes.py +++ b/src/api/bkuser_core/common/error_codes.py @@ -124,6 +124,7 @@ def __getattr__(self, code_name): ErrorCode("USER_ALREADY_EXISTED", _("该目录下此用户名已存在"), status_code=HTTP_409_CONFLICT), ErrorCode("SAVE_USER_INFO_FAILED", _("保存用户信息失败")), ErrorCode("PASSWORD_DUPLICATED", _("新密码不能与最近{max_password_history}次密码相同")), + ErrorCode("OLD_PASSWORD_ERROR", _("原密码校验失败")), # 上传文件相关 ErrorCode("FILE_IMPORT_TOO_LARGE", _("上传文件过大")), ErrorCode("FILE_IMPORT_FORMAT_ERROR", _("上传文件格式错误")), diff --git a/src/api/bkuser_core/config/common/system.py b/src/api/bkuser_core/config/common/system.py index df1800271..997633187 100644 --- a/src/api/bkuser_core/config/common/system.py +++ b/src/api/bkuser_core/config/common/system.py @@ -17,6 +17,11 @@ # 密码配置 # ============================================================================== +# 允许原始密码校验错误次数 +RESET_PASSWORD_OLD_PASSWORD_ERROR_MAX_COUNT = 3 +# 重置密码时对原始密码校验超限是否锁定 +ENABLE_RESET_PASSWORD_ERROR_PROFILE_LOCK = env.bool("ENABLE_RESET_PASSWORD_ERROR_PROFILE_LOCK", default=False) + # 最大密码长度(明文) PASSWORD_MAX_LENGTH = 32 # 重复密码最大历史数量 @@ -87,6 +92,9 @@ # 登录次数统计时间周期, 默认为一个月 LOGIN_RECORD_COUNT_SECONDS = env.int("LOGIN_RECORD_COUNT_SECONDS", default=60 * 60 * 24 * 30) +# 重置密码次数统计时间周期, 默认为十分钟 +RESET_PASSWORD_RECORD_COUNT_SECONDS = env.int("RESET_PASSWORD_RECORD_COUNT_SECONDS", default=60 * 10) + # sync, 用户管理本身做业务 HTTP API 数据源, 可以被另一个用户管理同步过去 # 复用 API, 接口参数中存在 SYNC_API_PARAM 时, 以sync的接口协议返回 SYNC_API_PARAM = "for_sync" diff --git a/src/api/bkuser_core/profiles/models.py b/src/api/bkuser_core/profiles/models.py index a1d2dfad5..36fc64fdc 100644 --- a/src/api/bkuser_core/profiles/models.py +++ b/src/api/bkuser_core/profiles/models.py @@ -180,6 +180,10 @@ def to_audit_info(self): def bad_check_cnt(self) -> int: return self.login_set.latest_failed_count() + @property + def bad_old_password_check_cnt(self): + return self.resetpassword_set.latest_check_old_password_failed_count() + @property def latest_check_time(self): return self.login_set.filter(is_success=False, reason=LogInFailReason.BAD_PASSWORD.value).latest().create_time diff --git a/src/api/bkuser_core/profiles/utils.py b/src/api/bkuser_core/profiles/utils.py index bdffa1f12..f468bf055 100644 --- a/src/api/bkuser_core/profiles/utils.py +++ b/src/api/bkuser_core/profiles/utils.py @@ -16,22 +16,27 @@ from typing import TYPE_CHECKING, Dict, Tuple from django.conf import settings -from django.contrib.auth.hashers import make_password +from django.contrib.auth.hashers import check_password, make_password from phonenumbers.phonenumberutil import UNKNOWN_REGION, country_code_for_region, region_code_for_country_code +from ..audit.constants import OperationStatus, OperationType, ResetPasswordFailReason from ..audit.models import ResetPassword from .exceptions import CountryISOCodeNotMatch, UsernameWithDomainFormatError +from bkuser_core.audit.utils import create_general_log, create_profile_log from bkuser_core.categories.cache import get_default_category_id_from_local_cache +from bkuser_core.common.error_codes import error_codes +from bkuser_core.profiles.constants import ProfileStatus +from bkuser_core.profiles.models import Profile from bkuser_core.profiles.validators import DOMAIN_PART_REGEX, USERNAME_REGEX from bkuser_core.user_settings.constants import InitPasswordMethod from bkuser_core.user_settings.loader import ConfigProvider from bkuser_global.local import local -if TYPE_CHECKING: - from bkuser_core.profiles.models import Profile - logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from rest_framework.request import Request + def gen_password(length): # 必须包含至少一个数字 @@ -245,3 +250,53 @@ def remove_sensitive_fields_for_profile(request, data: Dict) -> Dict: extras.pop(key) return data + + +def check_old_password(instance: "Profile", old_password: str, request: "Request"): + """原密码校验""" + raw_profile = Profile.objects.get(id=instance.id) + + if not check_password(old_password, raw_profile.password): + failed_reason = ResetPasswordFailReason.BAD_OLD_PASSWORD + try: + create_profile_log( + instance, + "ResetPassword", + {"is_success": False, "reason": failed_reason.value}, + request, + ) + except Exception: # pylint: disable=broad-except + logger.exception("failed to create reset password log") + + create_general_log( + operator=request.operator, + operate_type=OperationType.ADMIN_RESET_PASSWORD.value, + operator_obj=instance, + request=request, + status=OperationStatus.FAILED.value, + extra_info={"failed_info": ResetPasswordFailReason.get_choice_label(failed_reason.value)}, + ) + + if ( + instance.bad_old_password_check_cnt >= settings.RESET_PASSWORD_OLD_PASSWORD_ERROR_MAX_COUNT + and settings.ENABLE_RESET_PASSWORD_ERROR_PROFILE_LOCK + ): + # 校验失败次数超过配置次数并且配置锁定则对用户进行锁定 + raw_profile.status = ProfileStatus.LOCKED.value + raw_profile.save() + create_general_log( + operator=request.operator, + operate_type=OperationType.UPDATE.value, + operator_obj=instance, + request=request, + ) + + raise error_codes.OLD_PASSWORD_ERROR + + +def should_check_old_password(username: str) -> bool: + """重置密码时,校验是否为需要检查旧密码的用户""" + formatted_username = username.replace(" ", "").lower() + if not formatted_username == "admin": + return False + return True From b57b90af1803c0723e354bb03d0ea908ddb914c8 Mon Sep 17 00:00:00 2001 From: huacao Date: Wed, 14 Dec 2022 15:46:14 +0800 Subject: [PATCH 21/48] =?UTF-8?q?feature:=20admin=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E5=AF=86=E7=A0=81=E9=9C=80=E8=A6=81=E5=85=88?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E5=8E=9F=E5=AF=86=E7=A0=81=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=20#811?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/src/language/lang/en.js | 2 + src/pages/src/language/lang/zh.js | 2 + .../organization/details/UserMaterial.vue | 64 ++++++++++++++++--- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/pages/src/language/lang/en.js b/src/pages/src/language/lang/en.js index 7cf22d291..a6083918a 100644 --- a/src/pages/src/language/lang/en.js +++ b/src/pages/src/language/lang/en.js @@ -164,6 +164,7 @@ export default { 修复: '【FIX】', 优化: '【OPTIMIZATION】', // 密码 + 原密码: 'Old Password', 重置密码: 'Reset Password', 密码长度为: 'Password length is', '-32个字符,必须包含': '-32 characters, it must be included', @@ -187,6 +188,7 @@ export default { '两次输入的密码不一致,请重新输入': 'The passwords entered twice do not match. Please re-enter them', 设置新密码: 'Set new password', 请输入新密码进行密码重设: 'Please enter a new password for password reset', + 请输入原密码: 'Please enter old password', 请输入新密码: 'Please enter a new password', 请再次确认新密码: 'Please confirm the new password again', 密码修改成功: 'Password changed successfully', diff --git a/src/pages/src/language/lang/zh.js b/src/pages/src/language/lang/zh.js index 1d59f3424..d72704676 100644 --- a/src/pages/src/language/lang/zh.js +++ b/src/pages/src/language/lang/zh.js @@ -164,6 +164,7 @@ export default { 修复: '【修复】', 优化: '【优化】', // 密码 + 原密码: '原密码', 重置密码: '重置密码', 密码长度为: '密码长度为', '-32个字符,必须包含': '-32个字符,必须包含', @@ -187,6 +188,7 @@ export default { '两次输入的密码不一致,请重新输入': '两次输入的密码不一致,请重新输入', 设置新密码: '设置新密码', 请输入新密码进行密码重设: '请输入新密码进行密码重设', + 请输入原密码: '请输入原密码', 请输入新密码: '请输入新密码', 请再次确认新密码: '请再次确认新密码', 密码修改成功: '密码修改成功', diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index 558da3f1f..ef994d338 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -51,20 +51,35 @@
+

{{$t('重置密码')}}

- +

{{$t('确认')}} @@ -174,11 +189,16 @@ export default { localAvatar: '', isForbid: false, phoneNumber: this.$t('点击查看'), + isCorrectOldPw: false, isCorrectPw: false, // 是否显示重置密码的弹窗 isShowReset: false, + oldPassword: '', newPassword: '', - passwordInputType: 'password', + passwordInputType: { + oldPassword: 'password', + newPassword: 'password', + }, passwordRules: null, }; }, @@ -188,8 +208,11 @@ export default { return fieldInfo.key !== 'department_name' && fieldInfo.key !== 'leader'; }); }, + oldPasswordIconClass() { + return this.passwordInputType.oldPassword === 'password' ? 'icon-hide' : 'icon-eye'; + }, passwordIconClass() { - return this.passwordInputType === 'password' ? 'icon-hide' : 'icon-eye'; + return this.passwordInputType.newPassword === 'password' ? 'icon-hide' : 'icon-eye'; }, passwordValidDays() { return this.$store.state.passwordValidDaysList.find(item => ( @@ -286,6 +309,9 @@ export default { return; } this.isShowReset = true; + // 清空上次输入 + this.oldPassword = ''; + this.newPassword = ''; }, // 验证密码的格式 async confirmReset() { @@ -321,6 +347,18 @@ export default { } if (this.passwordRules) { // 如果上面拿到了规则就进行前端校验 + // 原密码校验, 任何人在重置admin密码时,需要先输入原密码 + if (this.isAdmin) { + this.isCorrectOldPw = !this.$validatePassportByRules(this.oldPassword, this.passwordRules); + if (this.isCorrectOldPw) { + this.$bkMessage({ + message: this.$getMessageByRules(this, this.passwordRules), + theme: 'error', + }); + return; + } + } + // 新密码校验 this.isCorrectPw = !this.$validatePassportByRules(this.newPassword, this.passwordRules); if (this.isCorrectPw) { this.$bkMessage({ @@ -336,11 +374,16 @@ export default { this.clickSecond = true; try { this.$emit('showBarLoading'); + const passwordData = { + password: this.newPassword.trim(), + }; + // 任何人在重置admin密码时,需要先输入原密码 + if (this.isAdmin) { + passwordData.old_password = this.oldPassword.trim(); + }; await this.$store.dispatch('organization/patchProfile', { id: this.currentProfile.id, - data: { - password: this.newPassword.trim(), - }, + data: passwordData, }); this.$bkMessage({ message: this.$t('重置密码成功'), @@ -357,10 +400,13 @@ export default { closeResetDialog(e) { if (e.target.innerText === '重置密码') return; this.isShowReset = false; + // 清空 + this.oldPassword = ''; + this.newPassword = ''; }, // 查看密码 - changePasswordInputType() { - this.passwordInputType = this.passwordInputType === 'password' ? 'text' : 'password'; + changePasswordInputType(type = 'newPassword') { + this.passwordInputType[type] = this.passwordInputType[type] === 'password' ? 'text' : 'password'; }, handleLoadAvatarError() { this.localAvatar = this.$store.state.localAvatar; From 4989e36fbf61bc204fdefbe24dfd6ec9a8379552 Mon Sep 17 00:00:00 2001 From: neronkl <49228807+neronkl@users.noreply.github.com> Date: Thu, 15 Dec 2022 12:31:07 +0800 Subject: [PATCH 22/48] =?UTF-8?q?fix:=20slz=20=E8=BD=AC=E4=B9=89display=5F?= =?UTF-8?q?name=20(#846)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: slz 转义display_name --- src/api/bkuser_core/api/web/profile/serializers.py | 9 ++++++++- src/api/bkuser_core/api/web/utils.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/api/bkuser_core/api/web/profile/serializers.py b/src/api/bkuser_core/api/web/profile/serializers.py index 729833469..212b67548 100644 --- a/src/api/bkuser_core/api/web/profile/serializers.py +++ b/src/api/bkuser_core/api/web/profile/serializers.py @@ -13,7 +13,7 @@ from rest_framework import serializers from bkuser_core.api.web.serializers import StringArrayField -from bkuser_core.api.web.utils import get_default_category_id, get_raw_password +from bkuser_core.api.web.utils import escape_value, get_default_category_id, get_raw_password from bkuser_core.profiles.models import Profile from bkuser_core.profiles.validators import validate_username @@ -106,6 +106,7 @@ class ProfileUpdateInputSLZ(serializers.ModelSerializer): leader = serializers.ListField(child=serializers.IntegerField(), required=False) departments = serializers.ListField(child=serializers.IntegerField(), required=False) password = serializers.CharField(required=False, write_only=True) + display_name = serializers.CharField(required=False) old_password = serializers.CharField(required=False, write_only=True) # 只有admin用户重置密码时才需要传递该字段 class Meta: @@ -116,6 +117,9 @@ class Meta: def validate_password(self, password): return get_raw_password(self.instance.category_id, password) + def validate_display_name(self, display_name): + return escape_value(display_name) + def validate_old_password(self, old_password): return get_raw_password(self.instance.category_id, old_password) @@ -173,6 +177,9 @@ class Meta: # exclude = ["password"] validators: list = [] + def validate_display_name(self, display_name): + return escape_value(display_name) + class ProfileBatchDeleteInputSLZ(serializers.Serializer): id = serializers.IntegerField() diff --git a/src/api/bkuser_core/api/web/utils.py b/src/api/bkuser_core/api/web/utils.py index 4829343d8..b682e4dbd 100644 --- a/src/api/bkuser_core/api/web/utils.py +++ b/src/api/bkuser_core/api/web/utils.py @@ -178,3 +178,17 @@ def get_token_handler(token: str) -> ProfileTokenHolder: raise error_codes.PROFILE_TOKEN_EXPIRED return token_holder + + +def escape_value(input_value: str) -> str: + """Replace special characters "&", "<" and ">" to HTML-safe sequences. + If the optional flag quote is true, the quotation mark character (") + is also translated. + rewrite the cgi method + """ + escaped_value = input_value.replace("&", "") # Must be done first! + escaped_value = escaped_value.replace("<", "") + escaped_value = escaped_value.replace(">", "") + escaped_value = escaped_value.replace('"', "") + escaped_value = escaped_value.replace("'", "") + return escaped_value From 7ef6234f9026bedc7133fab1cf5c70f12f46de73 Mon Sep 17 00:00:00 2001 From: caohua <317282606@qq.com> Date: Fri, 16 Dec 2022 10:49:22 +0800 Subject: [PATCH 23/48] =?UTF-8?q?feature:=20=E6=94=AF=E6=8C=81=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E9=87=8D=E7=BD=AE=E5=8F=AF=E9=80=89=E6=8B=A9=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1=E9=AA=8C=E8=AF=81=E6=96=B9=E5=BC=8F=20#631=20(#849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature: 支持密码重置可选择短信验证方式 #631 --- src/pages/src/common/demand-import.js | 2 + src/pages/src/language/lang/en.js | 6 + src/pages/src/language/lang/zh.js | 6 + src/pages/src/store/modules/password.js | 8 + src/pages/src/views/password/Email.vue | 151 ++++++++++++++++ src/pages/src/views/password/Reset.vue | 145 +++------------- src/pages/src/views/password/Set.vue | 8 +- src/pages/src/views/password/Sms.vue | 222 ++++++++++++++++++++++++ 8 files changed, 424 insertions(+), 124 deletions(-) create mode 100644 src/pages/src/views/password/Email.vue create mode 100644 src/pages/src/views/password/Sms.vue diff --git a/src/pages/src/common/demand-import.js b/src/pages/src/common/demand-import.js index 13959604a..8f78a2a8e 100644 --- a/src/pages/src/common/demand-import.js +++ b/src/pages/src/common/demand-import.js @@ -41,6 +41,7 @@ import { bkProgress, bkRadio, bkRadioGroup, + bkRadioButton, bkRoundProgress, bkRow, bkSearchSelect, @@ -100,6 +101,7 @@ Vue.use(bkProcess); Vue.use(bkProgress); Vue.use(bkRadio); Vue.use(bkRadioGroup); +Vue.use(bkRadioButton); Vue.use(bkRoundProgress); Vue.use(bkRow); Vue.use(bkSearchSelect); diff --git a/src/pages/src/language/lang/en.js b/src/pages/src/language/lang/en.js index a6083918a..b57b0faad 100644 --- a/src/pages/src/language/lang/en.js +++ b/src/pages/src/language/lang/en.js @@ -174,7 +174,13 @@ export default { 数字: 'number', '特殊字符(除空格)': 'Special characters (except spaces)', '请输入账户绑定的邮箱,我们将为您发送密码重置邮件': 'Please enter your account bound email address and we will send you a password reset email', + '请输入账号信息,我们将为您发送重置密码短信': 'Please enter your account information, and we will send you a message to reset your password', + '请输入用户名/手机号': 'Please enter username or mobile number', + 发送验证码: 'Send verification code', + 请输入验证码: 'Please enter the verification code', 请输入邮箱: 'Please enter email address', + 后: 'after', + 重新发送: 'Resend', '邮箱格式错误,请重新输入': 'Mailbox format error, please re - enter', 发送密码重置邮件: 'Send a password reset message', 已发送密码重置邮件: 'A password reset message has been sent', diff --git a/src/pages/src/language/lang/zh.js b/src/pages/src/language/lang/zh.js index d72704676..8c81e378e 100644 --- a/src/pages/src/language/lang/zh.js +++ b/src/pages/src/language/lang/zh.js @@ -174,6 +174,12 @@ export default { 数字: '数字', '特殊字符(除空格)': '特殊字符(除空格)', '请输入账户绑定的邮箱,我们将为您发送密码重置邮件': '请输入账户绑定的邮箱,我们将为您发送密码重置邮件', + '请输入账号信息,我们将为您发送重置密码短信': '请输入账号信息,我们将为您发送重置密码短信', + '请输入用户名/手机号': '请输入用户名/手机号', + 发送验证码: '发送验证码', + 请输入验证码: '请输入验证码', + 后: '后', + 重新发送: '重新发送', 请输入邮箱: '请输入邮箱', '邮箱格式错误,请重新输入': ' 邮箱格式错误,请重新输入', 发送密码重置邮件: '发送密码重置邮件', diff --git a/src/pages/src/store/modules/password.js b/src/pages/src/store/modules/password.js index 3c9e843b1..b9edf642c 100644 --- a/src/pages/src/store/modules/password.js +++ b/src/pages/src/store/modules/password.js @@ -33,5 +33,13 @@ export default { reset(context, params, config = {}) { return http.post('api/v1/web/passwords/reset/send_email/', params); }, + // 获取短信验证码 + sendSms(context, params, config = {}) { + return http.post('api/v1/web/passwords/reset/verification_code/send_sms/', params); + }, + // 发送验证码 + sendCode(context, params, config = {}) { + return http.post('api/v1/web/passwords/reset/verification_code/verify/', params); + }, }, }; diff --git a/src/pages/src/views/password/Email.vue b/src/pages/src/views/password/Email.vue new file mode 100644 index 000000000..d1c23e5a7 --- /dev/null +++ b/src/pages/src/views/password/Email.vue @@ -0,0 +1,151 @@ + + + + + + diff --git a/src/pages/src/views/password/Reset.vue b/src/pages/src/views/password/Reset.vue index 95faded4f..093e45538 100644 --- a/src/pages/src/views/password/Reset.vue +++ b/src/pages/src/views/password/Reset.vue @@ -25,71 +25,38 @@ -
@@ -138,75 +105,9 @@ export default { } } -.login-content { - padding: 0 24px; - font-size: 14px; -} - -.common-title { - margin: 20px 0 6px 0; - font-size: 20px; - font-weight: 400; - color: rgba(49, 50, 56, 1); - line-height: 28px; -} - -.select-text { - padding-left: 12px; - - &::input-placeholder { - color: rgba(195, 205, 215, 1); - } -} - -.submit { - width: 100%; -} -// 重置密码 -.reset-content { - .text { - font-size: 14px; - font-weight: 400; - color: rgba(99, 101, 110, 1); - line-height: 20px; - margin: 10px 0 20px; - - &.show-error-info { - margin-bottom: 10px; - } - } - - .select-text { - margin-bottom: 20px; - } -} -// 错误提示 -.error-text { - margin-bottom: 10px; - color: #ea3636; - font-size: 14px; - - .text { - color: #ea3636; - } - - .icon { - color: #ea3636; - } +.login-methond { + position: absolute; + right: 24px; + top: 120px; } - -/*.logo-title { - width: 100%; - height: 110px; - border-bottom: 1px solid #F0F1F5; - border-radius: 2px 2px 0 0; - background: #fff; - text-align: center; - line-height: 110px; - img { - height: 35px; - vertical-align: top; - margin-top: 37px; - } - }*/ diff --git a/src/pages/src/views/password/Set.vue b/src/pages/src/views/password/Set.vue index 2ca9bf412..34e6632b6 100644 --- a/src/pages/src/views/password/Set.vue +++ b/src/pages/src/views/password/Set.vue @@ -29,7 +29,11 @@

{{$t('设置新密码')}}

-

{{setPasswordText}}{{$t('_需要设置新密码')}}

+

+ {{setPasswordText}}{{$t('_需要设置新密码')}} +

{{errorText}} @@ -89,7 +93,7 @@ export default { isShow: false, title: this.$t('密码修改成功'), }, - setPasswordText: this.$route.query.data.substring(1, this.$route.query.data.length - 1), + setPasswordText: (this.$route.query.data || '').substring(1, (this.$route.query.data || '').length - 1), }; }, // mounted () { diff --git a/src/pages/src/views/password/Sms.vue b/src/pages/src/views/password/Sms.vue new file mode 100644 index 000000000..74f8407a3 --- /dev/null +++ b/src/pages/src/views/password/Sms.vue @@ -0,0 +1,222 @@ + + + + + + From 0a8c842c9961cfa392ae695c139c21cad9fe8a8c Mon Sep 17 00:00:00 2001 From: wklken Date: Fri, 16 Dec 2022 10:50:48 +0800 Subject: [PATCH 24/48] =?UTF-8?q?Revert=20"feature:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E9=87=8D=E7=BD=AE=E5=8F=AF=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E9=AA=8C=E8=AF=81=E6=96=B9=E5=BC=8F=20#631?= =?UTF-8?q?=20(#849)"=20(#850)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7ef6234f9026bedc7133fab1cf5c70f12f46de73. --- src/pages/src/common/demand-import.js | 2 - src/pages/src/language/lang/en.js | 6 - src/pages/src/language/lang/zh.js | 6 - src/pages/src/store/modules/password.js | 8 - src/pages/src/views/password/Email.vue | 151 ---------------- src/pages/src/views/password/Reset.vue | 145 +++++++++++++--- src/pages/src/views/password/Set.vue | 8 +- src/pages/src/views/password/Sms.vue | 222 ------------------------ 8 files changed, 124 insertions(+), 424 deletions(-) delete mode 100644 src/pages/src/views/password/Email.vue delete mode 100644 src/pages/src/views/password/Sms.vue diff --git a/src/pages/src/common/demand-import.js b/src/pages/src/common/demand-import.js index 8f78a2a8e..13959604a 100644 --- a/src/pages/src/common/demand-import.js +++ b/src/pages/src/common/demand-import.js @@ -41,7 +41,6 @@ import { bkProgress, bkRadio, bkRadioGroup, - bkRadioButton, bkRoundProgress, bkRow, bkSearchSelect, @@ -101,7 +100,6 @@ Vue.use(bkProcess); Vue.use(bkProgress); Vue.use(bkRadio); Vue.use(bkRadioGroup); -Vue.use(bkRadioButton); Vue.use(bkRoundProgress); Vue.use(bkRow); Vue.use(bkSearchSelect); diff --git a/src/pages/src/language/lang/en.js b/src/pages/src/language/lang/en.js index b57b0faad..a6083918a 100644 --- a/src/pages/src/language/lang/en.js +++ b/src/pages/src/language/lang/en.js @@ -174,13 +174,7 @@ export default { 数字: 'number', '特殊字符(除空格)': 'Special characters (except spaces)', '请输入账户绑定的邮箱,我们将为您发送密码重置邮件': 'Please enter your account bound email address and we will send you a password reset email', - '请输入账号信息,我们将为您发送重置密码短信': 'Please enter your account information, and we will send you a message to reset your password', - '请输入用户名/手机号': 'Please enter username or mobile number', - 发送验证码: 'Send verification code', - 请输入验证码: 'Please enter the verification code', 请输入邮箱: 'Please enter email address', - 后: 'after', - 重新发送: 'Resend', '邮箱格式错误,请重新输入': 'Mailbox format error, please re - enter', 发送密码重置邮件: 'Send a password reset message', 已发送密码重置邮件: 'A password reset message has been sent', diff --git a/src/pages/src/language/lang/zh.js b/src/pages/src/language/lang/zh.js index 8c81e378e..d72704676 100644 --- a/src/pages/src/language/lang/zh.js +++ b/src/pages/src/language/lang/zh.js @@ -174,12 +174,6 @@ export default { 数字: '数字', '特殊字符(除空格)': '特殊字符(除空格)', '请输入账户绑定的邮箱,我们将为您发送密码重置邮件': '请输入账户绑定的邮箱,我们将为您发送密码重置邮件', - '请输入账号信息,我们将为您发送重置密码短信': '请输入账号信息,我们将为您发送重置密码短信', - '请输入用户名/手机号': '请输入用户名/手机号', - 发送验证码: '发送验证码', - 请输入验证码: '请输入验证码', - 后: '后', - 重新发送: '重新发送', 请输入邮箱: '请输入邮箱', '邮箱格式错误,请重新输入': ' 邮箱格式错误,请重新输入', 发送密码重置邮件: '发送密码重置邮件', diff --git a/src/pages/src/store/modules/password.js b/src/pages/src/store/modules/password.js index b9edf642c..3c9e843b1 100644 --- a/src/pages/src/store/modules/password.js +++ b/src/pages/src/store/modules/password.js @@ -33,13 +33,5 @@ export default { reset(context, params, config = {}) { return http.post('api/v1/web/passwords/reset/send_email/', params); }, - // 获取短信验证码 - sendSms(context, params, config = {}) { - return http.post('api/v1/web/passwords/reset/verification_code/send_sms/', params); - }, - // 发送验证码 - sendCode(context, params, config = {}) { - return http.post('api/v1/web/passwords/reset/verification_code/verify/', params); - }, }, }; diff --git a/src/pages/src/views/password/Email.vue b/src/pages/src/views/password/Email.vue deleted file mode 100644 index d1c23e5a7..000000000 --- a/src/pages/src/views/password/Email.vue +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - diff --git a/src/pages/src/views/password/Reset.vue b/src/pages/src/views/password/Reset.vue index 093e45538..95faded4f 100644 --- a/src/pages/src/views/password/Reset.vue +++ b/src/pages/src/views/password/Reset.vue @@ -25,38 +25,71 @@

-
@@ -105,9 +138,75 @@ export default { } } -.login-methond { - position: absolute; - right: 24px; - top: 120px; +.login-content { + padding: 0 24px; + font-size: 14px; +} + +.common-title { + margin: 20px 0 6px 0; + font-size: 20px; + font-weight: 400; + color: rgba(49, 50, 56, 1); + line-height: 28px; +} + +.select-text { + padding-left: 12px; + + &::input-placeholder { + color: rgba(195, 205, 215, 1); + } +} + +.submit { + width: 100%; +} +// 重置密码 +.reset-content { + .text { + font-size: 14px; + font-weight: 400; + color: rgba(99, 101, 110, 1); + line-height: 20px; + margin: 10px 0 20px; + + &.show-error-info { + margin-bottom: 10px; + } + } + + .select-text { + margin-bottom: 20px; + } } +// 错误提示 +.error-text { + margin-bottom: 10px; + color: #ea3636; + font-size: 14px; + + .text { + color: #ea3636; + } + + .icon { + color: #ea3636; + } +} + +/*.logo-title { + width: 100%; + height: 110px; + border-bottom: 1px solid #F0F1F5; + border-radius: 2px 2px 0 0; + background: #fff; + text-align: center; + line-height: 110px; + img { + height: 35px; + vertical-align: top; + margin-top: 37px; + } + }*/ diff --git a/src/pages/src/views/password/Set.vue b/src/pages/src/views/password/Set.vue index 34e6632b6..2ca9bf412 100644 --- a/src/pages/src/views/password/Set.vue +++ b/src/pages/src/views/password/Set.vue @@ -29,11 +29,7 @@

{{$t('设置新密码')}}

-

- {{setPasswordText}}{{$t('_需要设置新密码')}} -

+

{{setPasswordText}}{{$t('_需要设置新密码')}}

{{errorText}} @@ -93,7 +89,7 @@ export default { isShow: false, title: this.$t('密码修改成功'), }, - setPasswordText: (this.$route.query.data || '').substring(1, (this.$route.query.data || '').length - 1), + setPasswordText: this.$route.query.data.substring(1, this.$route.query.data.length - 1), }; }, // mounted () { diff --git a/src/pages/src/views/password/Sms.vue b/src/pages/src/views/password/Sms.vue deleted file mode 100644 index 74f8407a3..000000000 --- a/src/pages/src/views/password/Sms.vue +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - From 9d0e1bf216988494976a1d4ec5d79b4e9d49b5c1 Mon Sep 17 00:00:00 2001 From: wklken Date: Fri, 16 Dec 2022 11:42:16 +0800 Subject: [PATCH 25/48] =?UTF-8?q?Revert=20"feature:=20admin=E8=B4=A6?= =?UTF-8?q?=E6=88=B7=E9=87=8D=E7=BD=AE=E5=AF=86=E7=A0=81=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E5=85=88=E8=BE=93=E5=85=A5=E5=8E=9F=E5=AF=86=E7=A0=81=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E6=A0=A1=E9=AA=8C=20#811"=20(#852)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit b57b90af1803c0723e354bb03d0ea908ddb914c8. --- src/pages/src/language/lang/en.js | 2 - src/pages/src/language/lang/zh.js | 2 - .../organization/details/UserMaterial.vue | 64 +++---------------- 3 files changed, 9 insertions(+), 59 deletions(-) diff --git a/src/pages/src/language/lang/en.js b/src/pages/src/language/lang/en.js index a6083918a..7cf22d291 100644 --- a/src/pages/src/language/lang/en.js +++ b/src/pages/src/language/lang/en.js @@ -164,7 +164,6 @@ export default { 修复: '【FIX】', 优化: '【OPTIMIZATION】', // 密码 - 原密码: 'Old Password', 重置密码: 'Reset Password', 密码长度为: 'Password length is', '-32个字符,必须包含': '-32 characters, it must be included', @@ -188,7 +187,6 @@ export default { '两次输入的密码不一致,请重新输入': 'The passwords entered twice do not match. Please re-enter them', 设置新密码: 'Set new password', 请输入新密码进行密码重设: 'Please enter a new password for password reset', - 请输入原密码: 'Please enter old password', 请输入新密码: 'Please enter a new password', 请再次确认新密码: 'Please confirm the new password again', 密码修改成功: 'Password changed successfully', diff --git a/src/pages/src/language/lang/zh.js b/src/pages/src/language/lang/zh.js index d72704676..1d59f3424 100644 --- a/src/pages/src/language/lang/zh.js +++ b/src/pages/src/language/lang/zh.js @@ -164,7 +164,6 @@ export default { 修复: '【修复】', 优化: '【优化】', // 密码 - 原密码: '原密码', 重置密码: '重置密码', 密码长度为: '密码长度为', '-32个字符,必须包含': '-32个字符,必须包含', @@ -188,7 +187,6 @@ export default { '两次输入的密码不一致,请重新输入': '两次输入的密码不一致,请重新输入', 设置新密码: '设置新密码', 请输入新密码进行密码重设: '请输入新密码进行密码重设', - 请输入原密码: '请输入原密码', 请输入新密码: '请输入新密码', 请再次确认新密码: '请再次确认新密码', 密码修改成功: '密码修改成功', diff --git a/src/pages/src/views/organization/details/UserMaterial.vue b/src/pages/src/views/organization/details/UserMaterial.vue index ef994d338..558da3f1f 100644 --- a/src/pages/src/views/organization/details/UserMaterial.vue +++ b/src/pages/src/views/organization/details/UserMaterial.vue @@ -51,35 +51,20 @@

-

{{$t('重置密码')}}

- +

{{$t('确认')}} @@ -189,16 +174,11 @@ export default { localAvatar: '', isForbid: false, phoneNumber: this.$t('点击查看'), - isCorrectOldPw: false, isCorrectPw: false, // 是否显示重置密码的弹窗 isShowReset: false, - oldPassword: '', newPassword: '', - passwordInputType: { - oldPassword: 'password', - newPassword: 'password', - }, + passwordInputType: 'password', passwordRules: null, }; }, @@ -208,11 +188,8 @@ export default { return fieldInfo.key !== 'department_name' && fieldInfo.key !== 'leader'; }); }, - oldPasswordIconClass() { - return this.passwordInputType.oldPassword === 'password' ? 'icon-hide' : 'icon-eye'; - }, passwordIconClass() { - return this.passwordInputType.newPassword === 'password' ? 'icon-hide' : 'icon-eye'; + return this.passwordInputType === 'password' ? 'icon-hide' : 'icon-eye'; }, passwordValidDays() { return this.$store.state.passwordValidDaysList.find(item => ( @@ -309,9 +286,6 @@ export default { return; } this.isShowReset = true; - // 清空上次输入 - this.oldPassword = ''; - this.newPassword = ''; }, // 验证密码的格式 async confirmReset() { @@ -347,18 +321,6 @@ export default { } if (this.passwordRules) { // 如果上面拿到了规则就进行前端校验 - // 原密码校验, 任何人在重置admin密码时,需要先输入原密码 - if (this.isAdmin) { - this.isCorrectOldPw = !this.$validatePassportByRules(this.oldPassword, this.passwordRules); - if (this.isCorrectOldPw) { - this.$bkMessage({ - message: this.$getMessageByRules(this, this.passwordRules), - theme: 'error', - }); - return; - } - } - // 新密码校验 this.isCorrectPw = !this.$validatePassportByRules(this.newPassword, this.passwordRules); if (this.isCorrectPw) { this.$bkMessage({ @@ -374,16 +336,11 @@ export default { this.clickSecond = true; try { this.$emit('showBarLoading'); - const passwordData = { - password: this.newPassword.trim(), - }; - // 任何人在重置admin密码时,需要先输入原密码 - if (this.isAdmin) { - passwordData.old_password = this.oldPassword.trim(); - }; await this.$store.dispatch('organization/patchProfile', { id: this.currentProfile.id, - data: passwordData, + data: { + password: this.newPassword.trim(), + }, }); this.$bkMessage({ message: this.$t('重置密码成功'), @@ -400,13 +357,10 @@ export default { closeResetDialog(e) { if (e.target.innerText === '重置密码') return; this.isShowReset = false; - // 清空 - this.oldPassword = ''; - this.newPassword = ''; }, // 查看密码 - changePasswordInputType(type = 'newPassword') { - this.passwordInputType[type] = this.passwordInputType[type] === 'password' ? 'text' : 'password'; + changePasswordInputType() { + this.passwordInputType = this.passwordInputType === 'password' ? 'text' : 'password'; }, handleLoadAvatarError() { this.localAvatar = this.$store.state.localAvatar; From 42e889a8c2ce3fc5d1cd0280e150b38424f54b1a Mon Sep 17 00:00:00 2001 From: caohua <317282606@qq.com> Date: Fri, 16 Dec 2022 15:40:15 +0800 Subject: [PATCH 26/48] =?UTF-8?q?feature:=20=E6=94=AF=E6=8C=81=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E9=87=8D=E7=BD=AE=E5=8F=AF=E9=80=89=E6=8B=A9=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1=E9=AA=8C=E8=AF=81=E6=96=B9=E5=BC=8F=20#631=20(#854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: huacao --- src/pages/src/common/demand-import.js | 2 + src/pages/src/language/lang/en.js | 11 ++ src/pages/src/language/lang/zh.js | 11 ++ src/pages/src/store/modules/password.js | 8 + src/pages/src/views/password/Email.vue | 151 ++++++++++++++++ src/pages/src/views/password/Reset.vue | 145 +++------------- src/pages/src/views/password/Set.vue | 8 +- src/pages/src/views/password/Sms.vue | 222 ++++++++++++++++++++++++ 8 files changed, 434 insertions(+), 124 deletions(-) create mode 100644 src/pages/src/views/password/Email.vue create mode 100644 src/pages/src/views/password/Sms.vue diff --git a/src/pages/src/common/demand-import.js b/src/pages/src/common/demand-import.js index 13959604a..8f78a2a8e 100644 --- a/src/pages/src/common/demand-import.js +++ b/src/pages/src/common/demand-import.js @@ -41,6 +41,7 @@ import { bkProgress, bkRadio, bkRadioGroup, + bkRadioButton, bkRoundProgress, bkRow, bkSearchSelect, @@ -100,6 +101,7 @@ Vue.use(bkProcess); Vue.use(bkProgress); Vue.use(bkRadio); Vue.use(bkRadioGroup); +Vue.use(bkRadioButton); Vue.use(bkRoundProgress); Vue.use(bkRow); Vue.use(bkSearchSelect); diff --git a/src/pages/src/language/lang/en.js b/src/pages/src/language/lang/en.js index 7cf22d291..4bf791a23 100644 --- a/src/pages/src/language/lang/en.js +++ b/src/pages/src/language/lang/en.js @@ -164,6 +164,7 @@ export default { 修复: '【FIX】', 优化: '【OPTIMIZATION】', // 密码 + 原密码: 'Old Password', 重置密码: 'Reset Password', 密码长度为: 'Password length is', '-32个字符,必须包含': '-32 characters, it must be included', @@ -173,7 +174,13 @@ export default { 数字: 'number', '特殊字符(除空格)': 'Special characters (except spaces)', '请输入账户绑定的邮箱,我们将为您发送密码重置邮件': 'Please enter your account bound email address and we will send you a password reset email', + '请输入账号信息,我们将为您发送重置密码短信': 'Please enter your account information, and we will send you a message to reset your password', + '请输入用户名/手机号': 'Please enter username or mobile number', + 发送验证码: 'Send verification code', + 请输入验证码: 'Please enter the verification code', 请输入邮箱: 'Please enter email address', + 后: 'after', + 重新发送: 'Resend', '邮箱格式错误,请重新输入': 'Mailbox format error, please re - enter', 发送密码重置邮件: 'Send a password reset message', 已发送密码重置邮件: 'A password reset message has been sent', @@ -187,6 +194,7 @@ export default { '两次输入的密码不一致,请重新输入': 'The passwords entered twice do not match. Please re-enter them', 设置新密码: 'Set new password', 请输入新密码进行密码重设: 'Please enter a new password for password reset', + 请输入原密码: 'Please enter old password', 请输入新密码: 'Please enter a new password', 请再次确认新密码: 'Please confirm the new password again', 密码修改成功: 'Password changed successfully', @@ -260,6 +268,8 @@ export default { 最近三个月: 'Last 3 months', 时间: 'Time', 操作人员: 'The operator', + 操作用户: 'The operator', + 用户全名: 'Full name', 操作对象: 'Action object', 操作类型: 'Operation type', 用户目录管理无权限访问: '【Categories Management】No access authority', @@ -446,4 +456,5 @@ export default { 退出登录: 'Log out', 确认退出登录_: '确认退出登录?', _需要设置新密码: ', You need to set a new password', + 导入覆盖提升: 'After checking, it is allowed to edit the personal information of existing users, and the existing user information will face the risk of being modified, so be cautious.', }; diff --git a/src/pages/src/language/lang/zh.js b/src/pages/src/language/lang/zh.js index 1d59f3424..b7c72c855 100644 --- a/src/pages/src/language/lang/zh.js +++ b/src/pages/src/language/lang/zh.js @@ -164,6 +164,7 @@ export default { 修复: '【修复】', 优化: '【优化】', // 密码 + 原密码: '原密码', 重置密码: '重置密码', 密码长度为: '密码长度为', '-32个字符,必须包含': '-32个字符,必须包含', @@ -173,6 +174,12 @@ export default { 数字: '数字', '特殊字符(除空格)': '特殊字符(除空格)', '请输入账户绑定的邮箱,我们将为您发送密码重置邮件': '请输入账户绑定的邮箱,我们将为您发送密码重置邮件', + '请输入账号信息,我们将为您发送重置密码短信': '请输入账号信息,我们将为您发送重置密码短信', + '请输入用户名/手机号': '请输入用户名/手机号', + 发送验证码: '发送验证码', + 请输入验证码: '请输入验证码', + 后: '后', + 重新发送: '重新发送', 请输入邮箱: '请输入邮箱', '邮箱格式错误,请重新输入': ' 邮箱格式错误,请重新输入', 发送密码重置邮件: '发送密码重置邮件', @@ -187,6 +194,7 @@ export default { '两次输入的密码不一致,请重新输入': '两次输入的密码不一致,请重新输入', 设置新密码: '设置新密码', 请输入新密码进行密码重设: '请输入新密码进行密码重设', + 请输入原密码: '请输入原密码', 请输入新密码: '请输入新密码', 请再次确认新密码: '请再次确认新密码', 密码修改成功: '密码修改成功', @@ -260,6 +268,8 @@ export default { 最近三个月: '最近三个月', 时间: '时间', 操作人员: '操作人员', + 操作用户: '操作用户', + 用户全名: '用户全名', 操作对象: '操作对象', 操作类型: '操作类型', 用户目录管理无权限访问: '【用户目录管理】无权限访问', @@ -445,4 +455,5 @@ export default { 退出登录: '退出登录', 确认退出登录_: '确认退出登录?', _需要设置新密码: ',需要设置新密码', + 导入覆盖提升: '勾选后,将允许对已经存在用户的个人信息进行编辑操作,现存用户信息将面临被修改风险,谨慎操作。', }; diff --git a/src/pages/src/store/modules/password.js b/src/pages/src/store/modules/password.js index 3c9e843b1..b9edf642c 100644 --- a/src/pages/src/store/modules/password.js +++ b/src/pages/src/store/modules/password.js @@ -33,5 +33,13 @@ export default { reset(context, params, config = {}) { return http.post('api/v1/web/passwords/reset/send_email/', params); }, + // 获取短信验证码 + sendSms(context, params, config = {}) { + return http.post('api/v1/web/passwords/reset/verification_code/send_sms/', params); + }, + // 发送验证码 + sendCode(context, params, config = {}) { + return http.post('api/v1/web/passwords/reset/verification_code/verify/', params); + }, }, }; diff --git a/src/pages/src/views/password/Email.vue b/src/pages/src/views/password/Email.vue new file mode 100644 index 000000000..d1c23e5a7 --- /dev/null +++ b/src/pages/src/views/password/Email.vue @@ -0,0 +1,151 @@ + + + + + + diff --git a/src/pages/src/views/password/Reset.vue b/src/pages/src/views/password/Reset.vue index 95faded4f..b22f771ef 100644 --- a/src/pages/src/views/password/Reset.vue +++ b/src/pages/src/views/password/Reset.vue @@ -25,71 +25,38 @@ -
@@ -138,75 +105,9 @@ export default { } } -.login-content { - padding: 0 24px; - font-size: 14px; -} - -.common-title { - margin: 20px 0 6px 0; - font-size: 20px; - font-weight: 400; - color: rgba(49, 50, 56, 1); - line-height: 28px; -} - -.select-text { - padding-left: 12px; - - &::input-placeholder { - color: rgba(195, 205, 215, 1); - } -} - -.submit { - width: 100%; -} -// 重置密码 -.reset-content { - .text { - font-size: 14px; - font-weight: 400; - color: rgba(99, 101, 110, 1); - line-height: 20px; - margin: 10px 0 20px; - - &.show-error-info { - margin-bottom: 10px; - } - } - - .select-text { - margin-bottom: 20px; - } -} -// 错误提示 -.error-text { - margin-bottom: 10px; - color: #ea3636; - font-size: 14px; - - .text { - color: #ea3636; - } - - .icon { - color: #ea3636; - } +.login-methond { + position: absolute; + right: 24px; + top: 120px; } - -/*.logo-title { - width: 100%; - height: 110px; - border-bottom: 1px solid #F0F1F5; - border-radius: 2px 2px 0 0; - background: #fff; - text-align: center; - line-height: 110px; - img { - height: 35px; - vertical-align: top; - margin-top: 37px; - } - }*/ diff --git a/src/pages/src/views/password/Set.vue b/src/pages/src/views/password/Set.vue index 2ca9bf412..34e6632b6 100644 --- a/src/pages/src/views/password/Set.vue +++ b/src/pages/src/views/password/Set.vue @@ -29,7 +29,11 @@

{{$t('设置新密码')}}

-

{{setPasswordText}}{{$t('_需要设置新密码')}}

+

+ {{setPasswordText}}{{$t('_需要设置新密码')}} +

{{errorText}} @@ -89,7 +93,7 @@ export default { isShow: false, title: this.$t('密码修改成功'), }, - setPasswordText: this.$route.query.data.substring(1, this.$route.query.data.length - 1), + setPasswordText: (this.$route.query.data || '').substring(1, (this.$route.query.data || '').length - 1), }; }, // mounted () { diff --git a/src/pages/src/views/password/Sms.vue b/src/pages/src/views/password/Sms.vue new file mode 100644 index 000000000..74f8407a3 --- /dev/null +++ b/src/pages/src/views/password/Sms.vue @@ -0,0 +1,222 @@ + + + + + + From a11c21c89b58b58e45283b89b0471bd7d9d80794 Mon Sep 17 00:00:00 2001 From: caohua <317282606@qq.com> Date: Fri, 16 Dec 2022 15:40:30 +0800 Subject: [PATCH 27/48] =?UTF-8?q?feature:=201=E3=80=81=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E4=B8=AD=E6=96=B0=E5=A2=9E=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E5=85=A8=E5=90=8D=E5=88=97&=E5=AF=BC=E5=87=BA=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BC=98=E5=8C=96=20#618=202=E3=80=81=E9=80=9A?= =?UTF-8?q?=E8=BF=87excel=E5=AF=BC=E5=85=A5=E7=94=A8=E6=88=B7=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E6=94=AF=E6=8C=81=E5=85=A8=E9=87=8F=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20#728=20(#855)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: huacao --- .../src/components/catalog/home/ImportUser.vue | 17 +++++++++++++++++ src/pages/src/store/modules/catalog.js | 2 +- src/pages/src/views/audit/index.vue | 8 ++++++-- src/pages/src/views/catalog/PageHome.vue | 6 ++++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/pages/src/components/catalog/home/ImportUser.vue b/src/pages/src/components/catalog/home/ImportUser.vue index 2226e491b..0d8caa368 100644 --- a/src/pages/src/components/catalog/home/ImportUser.vue +++ b/src/pages/src/components/catalog/home/ImportUser.vue @@ -48,6 +48,13 @@ {{$t('仅支持xls、xlsx格式文件')}} {{$t('下载模板')}}

+

+ + 允许对同名用户覆盖更新 + + +

@@ -69,6 +76,7 @@ export default { name: '', size: '', type: false, + isOverwrite: false, }, }; }, @@ -116,6 +124,7 @@ export default { name: '', size: '', type: false, + isOverwrite: false, }; }, // 上传 @@ -307,4 +316,12 @@ export default { cursor: pointer; } } + +.tip-wrapper { + margin-top: 10px; + .tip-icon { + display: inline-block; + transform: translateY(4px); + } +} diff --git a/src/pages/src/store/modules/catalog.js b/src/pages/src/store/modules/catalog.js index e51dbece8..c6592eb97 100644 --- a/src/pages/src/store/modules/catalog.js +++ b/src/pages/src/store/modules/catalog.js @@ -78,7 +78,7 @@ export default { }, // eslint-disable-next-line no-unused-vars ajaxImportUser(_context, params, _config = {}) { - return http.post(`api/v1/web/categories/${params.id}/operations/sync_or_import/`, params.data); + return http.post(`api/v1/web/categories/${params.id}/operations/sync_or_import/?is_overwrite=${params.isOverwrite}`, params.data); // const mockUrl = `?mock-file=catalog` // return http.get(mockUrl, config) }, diff --git a/src/pages/src/views/audit/index.vue b/src/pages/src/views/audit/index.vue index 2711e553a..12a48e417 100644 --- a/src/pages/src/views/audit/index.vue +++ b/src/pages/src/views/audit/index.vue @@ -39,7 +39,7 @@ v-if="panelActive === 'operate'" class="king-input-search" style="width: 400px;" - :placeholder="$t('搜索操作人员、操作对象、操作类型')" + :placeholder="$t('搜索操作用户、操作对象、操作类型')" :clearable="true" :left-icon="'bk-icon icon-search'" @clear="handleClear" @@ -71,6 +71,7 @@