import requests from django.conf import settings from django.core.cache import cache from pingcap_sso.models import PingCAPLarkStaff TENANT_ACCESS_TOEKN_CACHE_KEY = 'lark_tenant_access_token' request_session = requests.Session() request_session.timeout = 10 # https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal def get_tenant_access_token(): tenant_access_token = cache.get(TENANT_ACCESS_TOEKN_CACHE_KEY) if tenant_access_token: return tenant_access_token lark_token_endpoint = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' resp = request_session.post( lark_token_endpoint, json={ 'app_id': settings.PINGCAP_CN_LARK_APP_ID, 'app_secret': settings.PINGCAP_CN_LARK_APP_SECRET, } ) resp.raise_for_status() data = resp.json() if data['code'] != 0: raise Exception(data['msg']) tenant_access_token = data['tenant_access_token'] cache.set( TENANT_ACCESS_TOEKN_CACHE_KEY, tenant_access_token, timeout=data['expire'] - 10 ) return tenant_access_token # https://open.feishu.cn/document/server-docs/contact-v3/user/batch_get_id def batch_get_lark_user_ids(email_list, phone_list, include_resigned=False): lark_endpoint = 'https://open.feishu.cn/open-apis/contact/v3/users/batch_get_id' resp = request_session.post( lark_endpoint, json={ 'user_ids': [], 'emails': email_list, 'mobiles': phone_list, 'include_resigned': include_resigned, }, headers={ 'Authorization': f'Bearer {get_tenant_access_token()}', } ) resp.raise_for_status() data = resp.json() # it will return a list of user data, which contains user_id and email/mobile # it do not merge same user_id with different email/mobile # example data: # [ # { # "email": "xx@example.com", # "user_id": "ou_9b8b8xxxxx", # }, # { # "mobile": "18xxxxxxxxx", # "user_id": "ou_9b8b8xxxxx", # }, # ] return list(filter( lambda x: x.get('user_id'), data['data']['user_list'] )) # https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch def batch_get_users(lark_user_ids): if not lark_user_ids: return [] lark_endpoint = f'https://open.feishu.cn/open-apis/contact/v3/users/batch' resp = request_session.get( lark_endpoint, params={ 'user_ids': lark_user_ids, }, headers={ 'Authorization': f'Bearer {get_tenant_access_token()}', } ) resp.raise_for_status() data = resp.json() return list(filter( lambda x: x['status']['is_activated'] and not x['status']['is_resigned'], data['data']['items'] )) # https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch # raw data format can be found in the doc above def parse_lark_user_data(lark_user_data_raw): data = lark_user_data_raw user_id = data['open_id'] name = data['name'] or data['en_name'] github_id = None email = data['enterprise_email'] is_activated = data['status']['is_activated'] is_resigned = data['status']['is_resigned'] return { 'user_id': user_id, 'name': name, 'github_id': github_id, 'email': email, 'is_activated': is_activated, 'is_resigned': is_resigned, } def save_lark_user_to_db(lark_user_data): # avoid changing the original dict lark_user_data = lark_user_data.copy() lark_user_id = lark_user_data['user_id'] lark_staff, _ = PingCAPLarkStaff.objects.get_or_create(lark_user_id=lark_user_id) lark_user_data.pop('user_id') PingCAPLarkStaff.objects.filter(id=lark_staff.id).update(**lark_user_data) return lark_staff def search_lark_staff_by_user(user): emails = [] mobiles = [] if user.email and user.email_verified: emails = [user.email] if user.phone: mobiles = [user.phone] if not (emails or mobiles): return [] lark_user_ids = batch_get_lark_user_ids(emails, mobiles) lark_user_ids = list(set([user['user_id'] for user in lark_user_ids])) raw_lark_users = batch_get_users(lark_user_ids) data = [] for raw_lark_user in raw_lark_users: lark_user = parse_lark_user_data(raw_lark_user) data.append(lark_user) save_lark_user_to_db(lark_user) return data def unbind_lark_staff(user): PingCAPLarkStaff.objects.filter(user=user).update(user=None)