fangpsh's blog

Authentik 用户同步飞书员工属性

前面文章搞定了飞书作为oauth2 source,集成到了authentik,但是默认能拿到的信息非常有限,就这么几个

{
    "sub": "ou_caecc734c2e3328a62489fe0648c4b98779515d3",
    "name": "李雷",
    "picture": "https://www.feishu.cn/avatar",
    "open_id": "ou_caecc734c2e3328a62489fe0648c4b98779515d3",
    "union_id": "on_d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
    "en_name": "Lilei",
    "tenant_key": "736588c92lxf175d",
    "avatar_url": "www.feishu.cn/avatar/icon",
...
    "email": "zhangsan@feishu.cn",
    "user_id": "5d9bdxxx",
    "employee_no": "111222333",
    "mobile": "+86130xxxx0000"
}

最重要的,员工所在的组织架构,leader身份都没有,内部应用恰恰常常这些信息进行权限配置和审批流程内的审核等等。下游应用接入authentik之后,如果还需要单独去飞书拿这些信息,太麻烦了。

飞书或者企业微信返回的组织架构需要重建成树,这个过程比较耗时,如果在authentik 内部脚本做不太合适,只能用外部服务周期性同步和生成供查询。

想了个办法:

1. 服务A 定时周期性从飞书同步组织架构,重建树状的组织架构,保存到本地,供authentik 查询用户时,将组织层级信息写入用户信息;
2. Authentik 内 Property Mappings创建一个Scope Mapping,假设scope_name 为feishu_attribute。
  脚本主要逻辑:
     1. 检查当前request.user.attributes 是否包含feishu_attribute,若否,请求服务A更新飞书属性;
     2. 检查用户当前feishu_attribute_last_updated 是否过期,若是,更新飞书属性。
3. 创建的Provider,Advanced protocol settings -> Scopes,选择加入这个scope。
4. 内部应用来认证时申请的scope增加一个: feishu_attribute。

feishu_attribute scope mapping 简化如下, 外部服务返回的user 内容按需自定义:

import json
from datetime import datetime, timedelta

TIME_FORMAT="%Y-%m-%d %H:%M:%S.%f"

def update_user_feishu_attribute():
  url = 'https://xxxxx.oa.com/api/feishu/user/info'
  params = {'user_id': request.user.username,
            'with_department_list_info': True,
            'token': 'XXXXXXX'
            }
  resp = requests.get(url,params=params,timeout=3).json()
  if resp.get('code') == 0:
    user_data = resp.get('data').get('user')
    attributes = request.user.attributes
    attributes['feishu_attribute'] = user_data
    attributes['feishu_attribute_last_updated'] = datetime.now().strftime(TIME_FORMAT)
    request.user.update_attributes({})



if 'feishu_attribute' in request.user.attributes and 'feishu_attribute_last_updated' in request.user.attributes:
  now = datetime.now()
  last_updated = datetime.strptime(request.user.attributes['feishu_attribute_last_updated'], TIME_FORMAT)
  seconds_diff = (now - last_updated).total_seconds()
  if seconds_diff > 24 * 60 * 60:
    update_user_feishu_attribute()    
else:
  update_user_feishu_attribute()

return {
  "feishu_attribute": request.user.attributes.get('feishu_attribute', None),
}

scope mapping 的脚本自由度很大,可以完成各类需求,除了用户同步,还有例如自定义的登录判断等等,可玩性很高。