Giter Club home page Giter Club logo

tencentblueking / bk-user Goto Github PK

View Code? Open in Web Editor NEW
59.0 9.0 61.0 15.52 MB

蓝鲸用户管理是蓝鲸智云提供的企业组织架构和用户管理解决方案,为企业统一登录提供认证源服务。

License: MIT License

Makefile 0.17% Dockerfile 0.11% Python 65.23% Shell 0.05% JavaScript 2.39% HTML 2.54% Vue 23.34% CSS 0.47% Procfile 0.01% TypeScript 4.24% Less 1.48%
blueking python vue django django-rest-framework ldap active-directory organization

bk-user's Issues

审计支持失败记录

当前所有的审计都是在成功之后再记录,失败则“来去无痕”

应当为审计日志增加一个 status 字段标记操作是否成功,并能够记录失败的(已知)异常信息

数值型自定义字段在页面上输入时没有异常提示

复现方法:

  • 添加一个数值型自定义字段
  • 在组织架构页面编辑用户,在对应字段的编辑框输入

image

当输入不为数值时(例如任意字母),页面和输入框没有任何提示,输入数值时能正常显示

输入 e 时反而能正常显示

image

API 支持传递参数,可以拉取已软删除数据

当前所有的 API 请求,都会默认过滤掉 enabled=false 的数据,可能存在一些场景,用户想拉取被删除的数据,比如用来做审计。

一个想法:

显示声明返回结果需要包含软删除的数据

/api/v2/profiles?include_disabled=1&fields=username,id

返回数据:

"results": [
      {
        "id": 1,
        "username": "admin",
        # 即使在 fields 参数中没有 enabled, 但是由于 include_deleted 的存在,会默认添加该字段返回
        "enabled": true,
      },
      # 已软删除的数据
      {
        "id": 2,
        "username": "zhangsan",
        # 软删除判断标志
        "enabled": false,
      }
]     

改进部署 Helm Chart,降低部署门槛

  • 默认禁用权限中心配置,并添加手动注册权限中心的文档指引
  • ingress 资源修改成 networking/v1 版本
  • 修复当 mariadb 禁用时,envFrom 挂载失败问题
  • 增加 redis 内建存储,作为 Celery Broker
  • 修正若干文档 & 注释指引
  • 增加初始化管理员账号密码修改指引
  • 支持全局配置镜像 registry
  • service 名字添加前缀,和其他 Chart 区分

管理员可以在类似回收站的地方,恢复已(软)删除数据

现状

当前主要的资源数据: 用户 目录 部门 都是通过标记的方式(enabled=0)进行软删除,不同的是,创建时同步冲突的出路策略并不相同:

  • 前两者(用户 目录)创建遇到同 key 的已删除资源时,程序直接抛出错误
  • 部门 创建遇到同 key 的已删除资源时,则会恢复当前已删除的资源

这样的策略会带来一些使用上的困扰:当用户删除了某个用户名的用户,是无法通过产品重新添加同 username 的用户,需要手动在 DB 中删除数据才能重新添加。

为什么不直接复用?

因为 用户目录 资源是“权限敏感”的,它们常常会被关联到具体的权限项。

在后续的计划中,我们会在同一个目录支持不同的数据源,那么很可能存在一个情况,不同的自然人拥有相同的系统 username,这时候直接恢复已删除的用户,就可能出现权限转移的风险。

手动恢复

相较于程序后台静默地直接复用,提供一个产品功能,显式地告之操作者数据恢复的风险——恢复数据同时权限也将恢复,会更加明智。

AD目录插件支持同步自定义字段

功能需求背景,例如你遇到了什么问题

为什么会有这个点子?或者遇到了什么问题驱使你需要这个功能?

描述你觉得更好的解决方案

可以举例子来描述你觉得怎样做会更好

额外信息

任何你觉得有助于理解的信息

支持记录 LDAP/AD 同步组织架构/人员信息的结构化日志

功能需求背景,例如你遇到了什么问题

当前用户管理的目录同步是一个很缓慢的操作, 而且一旦用户刷新页面, 就会让同步完成与否充满不确定性,这样的用户体验很不友好。
因此希望能在产品(前端)上能看到 LADP/AD 同步组织架构和人员信息的过程和报错信息,而不是只能看机器的标准输出。

描述你觉得更好的解决方案

当前在同步 LDAP/AD 的组织架构和人员信息时, 未完全理清 DU 的含义, 导致代码像意大利面一样, 可读性较差,而目前的 Custom Plugin 类型的用户目录已经支持记录同步过程,如果能先重构 LADP/AD 的同步过程,使得流程更清晰,那么就可以更方便地记录同步过程了。

前端在用户设置选择上级用户报错

API 地址

api/v2/categories/%s/profiles/$

应用

saas

报错信息

KeyError: "Got KeyError when attempting to get a value for field emailon serializerProfileSerializer.\nThe serializer field might be named incorrectly and not match any attribute or key on the dictinstance.\nOriginal exception text was: 'email'."

建议解决

saas ---> bk_shell ---> organization ---> views ---> profiles.py

原始代码

    def list(self, request, category_id, validated_data):
        lookup_param = {
            "page": validated_data["page"],
            "page_size": validated_data["page_size"],
            "lookup_field": "category_id",
            "exact_lookups": [
                category_id,
            ],
        }
        # 全量用户返回体过大,只选择某些字段展示
        if validated_data["no_page"]:
            lookup_param.update({"fields": ["id", "username", "display_name"]})

修改后的代码

    def list(self, request, category_id, validated_data):
        lookup_param = {
            "page": validated_data["page"],
            "page_size": validated_data["page_size"],
            "lookup_field": "category_id",
            "exact_lookups": [
                category_id,
            ],
        }
        # 全量用户返回体过大,只选择某些字段展示
        if validated_data["no_page"]:
            lookup_param.update({"fields": ["id", "username", "display_name", "email", "iso_code", "departments"]})

支持敏感信息设置

因为某些字段(例如手机号、微信号)是非常敏感的,不便于直接在 SaaS 上展示,考虑在【目录设置】中增加【敏感信息设置】

  • 用户可以设置某些字段作为敏感信息,默认产品上是脱敏的,如果需要查看,要做二次验证 #34
  • 保持兼容,暂时对接口拉取无限制

AD目录插件支持同步多个OU

功能需求背景,例如你遇到了什么问题

为什么会有这个点子?或者遇到了什么问题驱使你需要这个功能?

描述你觉得更好的解决方案

可以举例子来描述你觉得怎样做会更好

额外信息

任何你觉得有助于理解的信息

直接以 {category-id}-{dn} 来作为 LDAP/MAD 的 code 标识

https://github.com/TencentBlueKing/bk-user/blob/master/src/api/bkuser_core/categories/plugins/ldap/syncer.py#L103

def _get_code(self, raw_obj: dict) -> str:
        """如果不存在 uuid 则用 dn(sha) 作为唯一标示"""
        entry_uuid = raw_obj.get("raw_attributes", {}).get("entryUUID", [])
        if isinstance(entry_uuid, list) and entry_uuid:
            logger.debug("uuid in raw_attributes: return %s", entry_uuid[0])
            return entry_uuid[0]
        else:
            # 由于其他目录也可能会出现这样的 code,所以添加 category_id 进行转换
            dn = f"{self.category_id}-{raw_obj.get('dn')}"

            sha = hashlib.sha256(force_bytes(dn)).hexdigest()
            logger.debug("no uuid in raw_attributes, use dn instead: %s -> %s", dn, sha)
            return sha

当前我们主要是以 entryUUID 优先作为 code 字段,但是由于很多情况目标服务返回的 raw_attributes 中没有该字段,而通过 sha256 保存的 code 基本不可读,所以考虑是否直接使用 {category-id}-{dn} 这样的拼接来作为 code

需要考虑的问题:

  • dn 的长度可能会超长,需要做一个超长控制,如果直接超长截取需要考虑 code 重复问题(添加随机?)
  • 现有数据是否会有兼容性问题

批量获取用户接口/api/v2/batch/profiles/,swagger显示的是没有参数,但实际代码中校验参数query_ids

访问批量获取用户接口/api/v2/batch/profiles/,返回错误:
{"result":false,"data":null,"code":-1,"message":"request failed, please check api log of bk-user"}

源码:
bkuser_core-->common-->viewset.py的mutliple_retrieve获取query_ids

建议修改文件 bkuser_core/profiles/views.py:

@swagger_auto_schema(
manual_parameters=[],
responses={"200": local_serializers.ProfileSerializer()},
tags=["profiles"],
)

bkuser_core/profiles/views.py文件增加导入 openapi

from drf_yasg import openapi

swagger部分修改:

@swagger_auto_schema(
    manual_parameters=[openapi.Parameter("query_ids", openapi.IN_QUERY,
                                         description="input departments query_ids,逗号分割",
                                         type=openapi.TYPE_STRING)],
    responses={"200": local_serializers.ProfileSerializer(many=True)},
)

API 支持通过 POST body 筛选数据

当前我们提供了列表拉取 API,形如

api/v2/profiles?exact_lookups=foo,bar,baz

可以用来筛选特定用户名列表的用户,但是有些场景中这个用户名列表会非常巨大,使用 GET 方法会导致 URL 超长。
为此我们提供了一个特定 HEADER 支持用户使用 POST 达到 GET 效果

但是如果请求 body 本身已经非常大了,会触发 gunicorn 的限制

所以最好能够在用户使用覆盖 HEADER 时,能够告知处理端从 POST Body 中读取参数,而不是 query_params

用户密码过期后处理方案

当用户的密码过期后,目前只能管理员重置用户密码

应该在提示密码过期后面,提供「修改密码」入口

支持二次登录认证

支持用户通过短信动态码等方式二次确认登陆

需要将登陆代码挪到用户管理维护(#5)后才能进一步开展设计。

将数据源插件和目录类型解耦

当前我们可以创建不同的目录,不同的目录对应不同的数据集合,数据的命名空间(尤其是 username)是相互独立的,同时目录类型本身和同步方式是一一绑定的,这样会带来一些使用上的麻烦,所以我们希望能够针对二者做解耦

现状

image

存在几个内建的 category.type 直接对应同步方式(插件)

  • local
  • ldap
  • mad
  • custom

同时,为了兼容自定义插件的场景,临时增加了一种特殊的类型pluggable 作为过渡,当目录类型是 pluggable 时会尝试从 settings 中获取对应的插件类型。

def get_plugin_by_category(category: "ProfileCategory") -> "DataSourcePlugin":
    """通过 category 类型获取插件名"""
    if category.type == CategoryType.PLUGGABLE.value:
        plugin_name = ConfigProvider(category.id)[PLUGIN_NAME_SETTING_KEY]
        return get_plugin_by_name(plugin_name)

    for n, p in _global_plugins.items():
        if p.category_type == category.type:
            return p
    raise ValueError(f"Plugin with category type: {category.type} does not exist")

同时也会带来一些问题:

  • 同一种类型的目录只能同步一种数据类型。当系统已经在公司内铺开使用后,各个体系产品都已经存储了默认目录的用户名,如果要修改目录的默认属性,会带来巨量的数据一致性挑战。
  • 当我们做多租户改造时,目录将转换成租户组的概念,隔离属性将被复用,但是数据源绑定属性则不应该放在租户组中。

改造

所以我们需要将二者完全解耦。达到的效果就是:

  • 同一个目录(租户组)可以添加多种数据源同步插件,可以同步多种数据源
  • 数据源插件之间的数据不再具有隔离性,将共享命名空间
  • 每一个数据本身将添加一个“数据源标记”,例如,用户在登录时,将通过这个标记找寻到对应的插件登录逻辑,进而统一蓝鲸登录插件和用户管理数据源插件

image

数据迁移

为了保证整体的平滑,我们将已有数据转换概念,一次性升级

image

增加组织架构数据正确性检测脚本

在企业中部署时,常常会手动修改数据库情况,由于代码本身无法感知,会引发一些程序异常。

例如:

  • departmentenabled 字段手动修改成 0,但仍被 enabled=1 的子部门关联,此时权限中心同步接口将出现无法建树的情况

可以提供一个扫描脚本,对于有关联关系的数据模型,判断其关联是否有效,输出一个报告,辅助实施侧运维判断。

优化数据源插件的配置方式

当前插件的数据配置需要插件开发者手动配置 Settings,也没有页面交互,非常不友好,给插件开发带来了较高的门槛

注册时声明配置

目前的注册

DataSourcePlugin(
    name="ldap",
    syncer_cls=LDAPSyncer,
    login_handler_cls=LoginHandler,
    allow_client_write=False,
    category_type="ldap",
    extra_config={"default_sync_period": 60, "min_sync_period": 60, "ldap_max_paged_size": 1000},
).register()

开发者可以在这里通过 extra_config 注册一些同步配置,但是只有简单的 key-value 模式,相当于只能在填入开发时的默认值,无法在插件使用时配置。

利用 Python Type Annotation 声明配置

@dataclass
class PluginConfig:
    """使用者需要关注的配置"""
    # 字符枚举类型,无默认值
    basic_pull_node: Literal["foo", "bar"]
    # 具备默认值
    default_sync_period: int = 60
    user_filter_class: str = "(objectClass=user)"

    @dataclass
    class Private:
        """仅在同步内部使用的配置"""
        # 开发内部使用,不会暴露到页面配置
        min_sync_period: int = 60

用户在注册时需要传入配置类

DataSourcePlugin(
    name="ldap",
    syncer_cls=LDAPSyncer,
    login_handler_cls=LoginHandler,
    allow_client_write=False,
    category_type="ldap",
    extra_config_cls=PluginConfig,
).register()

然后,Api 需要在注册时,将 PluginConfig 描述的配置创建成 SettingsMeta

同时需要提供一个配置接口,将 dataclass 转换成配置描述:

{
  "basic_pull_node": {
    "type": "enum",
    "required": "true",
    "default": null,
    "choices": ["foo", "bar"]
  },
  "default_sync_period": {
    "type": "int",
    "required": "false",
    "default": 60,
    "choices": null
  },
  "user_filter_class": {
    "type": "str",
    "required": "true",
    "default": "(objectClass=user)",
    "choices": null
  }
}

在 SaaS 侧前端需要解析这样的 JSON 描述,然后渲染成配置页面。

将散落多个路径中的 .gitignore 合并到顶层 .gitignore

  • src/api/bkuser_core/config/overlays/.gitignore
  • src/api/.gitignore
  • src/saas/bkuser_shell/config/overlays/.gitignore
  • src/saas/.gitignore
  • src/pages/.gitignore
  • deploy/helm/.gitignore

(src/sdk 中还有一个,不过由于都是 swagger-codegen 生成的,所以忽略问题不大)

更方便转换成正确的 .dockerignore

增加版本升级[不兼容]需知文档

从原来的 2.2.x -> 2.3.x 有一些不兼容的修改:

  • 移除了原来的 v1 查询接口
  • 去除了 no_page 查询参数

提供修改原因,和如何迁移到新方案的指引。

文字按钮的风格保持统一

image

image

二者的字号、颜色应该保持统一

(个人认为 “数据更新记录” 的色号更合适,而 “审计导出” 的字号更合适)

未关联部门的用户应该有地方展示

当前用户如果没有关联到具体的部门,在 SaaS 的组织架构上是无法展示的(可以搜索到),应该有一个专门的地方用来展示这些孤立的用户,并支持管理员手动给他们关联到具体部门。

邮箱、手机号支持唯一性校验

需要考虑存量数据可能存在重复问题,添加脚本检测更新?

可能涉及的改造内容:

  • 用户登录时支持更直接的手机号、邮箱登录(分 tab?)
  • 可以通过邮箱或者手机号重置密码

前提是完成登陆代码整合维护 (#5 )

请求接口/api/v2/setting_metas/base_dn/,返回request failed, please check api log of bk-user

请求接口/api/v2/setting_metas/base_dn/,返回错误request failed, please check api log of bk-user

检查日志报错大致如下:
bkuser_core.user_settings.models.SettingMeta.MultipleObjectsReturned: get() returned more than one SettingMeta -- it returned 2!

而表结构的主键是:unique_together = ("key", "namespace", "category_type")

而lookup_field的值是key:lookup_field = "key"

因为调用的是get_object(),这个最终调用的是django--->shortcuts.py的

return queryset.get(*args, **kwargs)

建议修改如下:
文件:bkuser_core/user_settings/views.py
原内容:

class SettingMetaViewSet(AdvancedModelViewSet, AdvancedListAPIView):
    """配置信息"""

    queryset = SettingMeta.objects.all()
    serializer_class = serializers.SettingMetaSerializer
    lookup_field = "key"

修改内容:

class SettingMetaViewSet(AdvancedModelViewSet, AdvancedListAPIView):
    """配置信息"""

    queryset = SettingMeta.objects.all()
    serializer_class = serializers.SettingMetaSerializer
    lookup_field = "id"

整合蓝鲸登录

目前蓝鲸登录是放在 蓝鲸 PaaS 平台 中维护的,而实际上在体系中,登录和用户管理的关系更加紧密一些,所以将登录的代码整合在用户管理看起来是更合理的方向。

整合之后,可以更方便的支持一些登录相关功能:

  • 首次登录强制修改密码
  • 统一插件方案:数据同步源插件 + 自定义登录插件
  • 两步验证

用户首次登录强制修改密码

背景: 企业版3.0部署后,admin的密码是后台生成的随机密码,产品上需要增加用户首次登录蓝鲸强制修改密码的开关(默认开启),这样不管是admin还是其他用户,第一次登录都会强制修改密码。

需要将登陆代码挪到用户管理维护(#5)后才能进一步开展设计。

发布支持【二进制/Smart】格式包

用文字描述你遇到的问题

请用简练的文字描述你遇到的问题,问题描述的清晰程度决定了问题被解决的效率。

重现方法

请描述问题重现的方法,如果不方便描述,可以通过截图或者视频辅助。

预期行为

预期的正常行为

版本

  • 提供用户管理的具体版本号
  • 是否是企业版问题?

如果是 SaaS 页面问题,请提供使用的操作系统和浏览器信息

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

额外信息

任何你觉得有助于问题解决的内容

修正 SettingMeta 默认路径参数为 id

正如 #44 中描述,由于 key 并不是全局唯一的,不适合作为路径参数,需要修正为 id

class SettingMetaViewSet(AdvancedModelViewSet, AdvancedListAPIView):
    """配置信息"""

    queryset = SettingMeta.objects.all()
    serializer_class = serializers.SettingMetaSerializer
    lookup_field = "id"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.