From de28ef06bb1270d83d18655fc79b765900a92aa5 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 12:25:15 +0800 Subject: [PATCH 01/35] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/settings.yml b/config/settings.yml index 204925b..c6a6870 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -4,6 +4,7 @@ login: client: true oauth2_client_id: "" oauth2_client_secret: "" + oauth2_client_corpid: "" oauth2_authorize_url: "" oauth2_authorize_signup_url: "" oauth2_token_url: "" @@ -42,7 +43,8 @@ login: oauth2_authorize_options: default: "scope" type: list - oauth2_scope: "" + scope: "" + oauth2_json_unionid_path: "unionid" oauth2_button_title: default: "with OAuth2" client: true @@ -51,3 +53,6 @@ login: oauth2_disable_csrf: default: false hidden: true + oauth2_email_domain: + default: "tmp.renogy.com" + client: true \ No newline at end of file From 0b613a0e161110bdeca7c89e765a24a916fa2379 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 13:56:41 +0800 Subject: [PATCH 02/35] =?UTF-8?q?=E9=87=8D=E5=86=99=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.yml | 3 + lib/DingtalkAuthenticator.rb | 167 +++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 lib/DingtalkAuthenticator.rb diff --git a/config/settings.yml b/config/settings.yml index c6a6870..7bdad09 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -55,4 +55,7 @@ login: hidden: true oauth2_email_domain: default: "tmp.renogy.com" + client: true + dingtalk_enable_cache: + default: false client: true \ No newline at end of file diff --git a/lib/DingtalkAuthenticator.rb b/lib/DingtalkAuthenticator.rb new file mode 100644 index 0000000..1d6bb22 --- /dev/null +++ b/lib/DingtalkAuthenticator.rb @@ -0,0 +1,167 @@ +# dingtalk_authenticator.rb +class DingtalkAuthenticator < OAuth2BasicAuthenticator + def name + "dingtalk" + end + + # 带缓存版本(生产环境使用) + def fetch_user_details_with_cache(token, unionid) + log "[钉钉认证] 开始获取用户详情(缓存版),unionid: #{unionid}" + + corp_token = cached_corp_access_token + return log_failure("企业Token获取失败") unless corp_token + + userid = cached_userid_lookup(corp_token, unionid) + return log_failure("UserID查询失败") unless userid + + user_details = cached_user_details(corp_token, userid) + return log_failure("用户详情获取失败") unless user_details + + parse_details(user_details).tap do |result| + log "[钉钉认证] 用户详情获取成功: #{result.inspect}" + end + end + + # 无缓存版本(调试使用) + def fetch_user_details_without_cache(token, unionid) + log "[钉钉认证] 开始获取用户详情(无缓存版),unionid: #{unionid}" + + corp_token = uncached_corp_access_token + return log_failure("企业Token获取失败") unless corp_token + + userid = uncached_userid_lookup(corp_token, unionid) + return log_failure("UserID查询失败") unless userid + + user_details = uncached_user_details(corp_token, userid) + return log_failure("用户详情获取失败") unless user_details + + parse_details(user_details).tap do |result| + log "[钉钉认证] 用户详情获取完成: #{result.inspect}" + end + end + + # 根据配置选择模式 + def fetch_user_details(token, unionid) + if SiteSetting.dingtalk_enable_cache + fetch_user_details_with_cache(token, unionid) + else + fetch_user_details_without_cache(token, unionid) + end + end + + private + + #===== 带缓存方法 ===== + def cached_corp_access_token + cache_key = "dingtalk_corp_token_v2_#{SiteSetting.oauth2_client_id}" + + Discourse.cache.fetch(cache_key, expires_in: 7100) do + log "[缓存] 企业Token缓存未命中,重新获取" + uncached_corp_access_token + end + end + + def cached_userid_lookup(corp_token, unionid) + cache_key = "dingtalk_unionid_mapping_#{unionid}" + + Discourse.cache.fetch(cache_key, expires_in: 86400) do + log "[缓存] UnionID映射缓存未命中,重新查询" + uncached_userid_lookup(corp_token, unionid) + end + end + + def cached_user_details(corp_token, userid) + cache_key = "dingtalk_user_details_#{userid}" + + Discourse.cache.fetch(cache_key, expires_in: 3600) do + log "[缓存] 用户详情缓存未命中,重新获取" + uncached_user_details(corp_token, userid) + end + end + + #===== 无缓存方法 ===== + def uncached_corp_access_token + log "[HTTP] 请求企业Token | AppKey: #{SiteSetting.oauth2_client_id}" + + response = Faraday.post( + "https://api.dingtalk.com/v1.0/oauth2/accessToken", # 最新API地址 + { + appKey: SiteSetting.oauth2_client_id, + appSecret: SiteSetting.oauth2_client_secret + }.to_json, + { + "Content-Type" => "application/json", + "Accept" => "application/json" + } + ) + + log_response(response, "企业Token") + JSON.parse(response.body)["accessToken"] rescue nil + end + + def uncached_userid_lookup(corp_token, unionid) + log "[HTTP] 查询UserID | UnionID: #{unionid}" + + response = Faraday.post( + "https://api.dingtalk.com/v1.0/contact/users/unionId/get", # 最新API地址 + { unionId: unionid }.to_json, + { + "Content-Type" => "application/json", + "x-acs-dingtalk-access-token" => corp_token + } + ) + + log_response(response, "UserID查询") + JSON.parse(response.body)["userId"] rescue nil + end + + def uncached_user_details(corp_token, userid) + log "[HTTP] 获取用户详情 | UserID: #{userid}" + + response = Faraday.get( + "https://api.dingtalk.com/v1.0/contact/users/#{userid}", # 最新API地址 + nil, + { + "x-acs-dingtalk-access-token" => corp_token + } + ) + + log_response(response, "用户详情") + JSON.parse(response.body) rescue nil + end + + #===== 通用方法 ===== + def parse_details(data) + { + user_id: data.dig("unionId"), + username: data.dig("nick"), + name: data.dig("name"), + email: data.dig("email") || fallback_email(data), + avatar: data.dig("avatarUrl") + }.tap do |result| + result[:email_verified] = email_verified?(result) + end + end + + def fallback_email(data) + "#{data['unionId']}@#{SiteSetting.oauth2_email_domain}" + end + + def email_verified?(details) + details[:email].present? && details[:email].include?("@") + end + + def log_response(response, api_name) + log <<~LOG + [HTTP响应] #{api_name} API + 状态码: #{response.status} + 响应头: #{response.headers.to_h.to_json} + 响应体: #{response.body.inspect} + LOG + end + + def log_failure(message) + log "[错误] #{message}" + nil + end +end \ No newline at end of file From 646395daaa96b76faf214d07e0145f7fe20a2e0b Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 14:04:19 +0800 Subject: [PATCH 03/35] =?UTF-8?q?=E9=87=8D=E5=86=99=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/DingtalkAuthenticator.rb | 242 +++++++++++++++++++---------------- 1 file changed, 135 insertions(+), 107 deletions(-) diff --git a/lib/DingtalkAuthenticator.rb b/lib/DingtalkAuthenticator.rb index 1d6bb22..64073dc 100644 --- a/lib/DingtalkAuthenticator.rb +++ b/lib/DingtalkAuthenticator.rb @@ -4,106 +4,144 @@ def name "dingtalk" end - # 带缓存版本(生产环境使用) - def fetch_user_details_with_cache(token, unionid) - log "[钉钉认证] 开始获取用户详情(缓存版),unionid: #{unionid}" + # 核心认证流程 + def after_authenticate(auth, existing_account: nil) + log "[钉钉] 开始认证流程" + + # 1. 通过授权码获取用户访问令牌 + user_token = get_user_access_token(auth[:code]) + return auth_failed("用户令牌获取失败") unless user_token + + # 2. 获取基础用户信息(包含unionId) + base_info = get_base_user_info(user_token) + return auth_failed("基础信息获取失败") unless base_info + unionid = base_info.dig("unionId") + + # 3. 获取详细用户信息 + user_details = fetch_user_details(unionid) + return auth_failed("用户详情获取失败") unless user_details + + # 4. 构建认证结果 + build_auth_result(user_details).tap do |result| + log "[钉钉] 认证完成: #{result.inspect}" + end + end - corp_token = cached_corp_access_token - return log_failure("企业Token获取失败") unless corp_token + private - userid = cached_userid_lookup(corp_token, unionid) - return log_failure("UserID查询失败") unless userid + # 获取用户访问令牌 + def get_user_access_token(code) + log "[HTTP] 获取用户访问令牌 | code: #{code[0..3]}***" - user_details = cached_user_details(corp_token, userid) - return log_failure("用户详情获取失败") unless user_details + response = Faraday.post( + "https://api.dingtalk.com/v1.0/oauth2/userAccessToken", + { + clientId: SiteSetting.oauth2_client_id, + clientSecret: SiteSetting.oauth2_client_secret, + code: code, + grantType: "authorization_code" + }.to_json, + { + "Content-Type" => "application/json", + "Accept" => "application/json" + } + ) - parse_details(user_details).tap do |result| - log "[钉钉认证] 用户详情获取成功: #{result.inspect}" - end + log_response(response, "用户令牌") + JSON.parse(response.body)["accessToken"] rescue nil end - # 无缓存版本(调试使用) - def fetch_user_details_without_cache(token, unionid) - log "[钉钉认证] 开始获取用户详情(无缓存版),unionid: #{unionid}" - - corp_token = uncached_corp_access_token - return log_failure("企业Token获取失败") unless corp_token - - userid = uncached_userid_lookup(corp_token, unionid) - return log_failure("UserID查询失败") unless userid + # 获取基础用户信息 + def get_base_user_info(user_token) + log "[HTTP] 获取基础用户信息" - user_details = uncached_user_details(corp_token, userid) - return log_failure("用户详情获取失败") unless user_details + response = Faraday.get( + "https://api.dingtalk.com/v1.0/contact/users/me", + nil, + { + "x-acs-dingtalk-access-token" => user_token + } + ) - parse_details(user_details).tap do |result| - log "[钉钉认证] 用户详情获取完成: #{result.inspect}" - end + log_response(response, "基础信息") + JSON.parse(response.body) rescue nil end - # 根据配置选择模式 - def fetch_user_details(token, unionid) + # 获取详细用户信息(带缓存控制) + def fetch_user_details(unionid) if SiteSetting.dingtalk_enable_cache - fetch_user_details_with_cache(token, unionid) + fetch_with_cache(unionid) else - fetch_user_details_without_cache(token, unionid) + fetch_without_cache(unionid) end end - private - - #===== 带缓存方法 ===== - def cached_corp_access_token - cache_key = "dingtalk_corp_token_v2_#{SiteSetting.oauth2_client_id}" + # 带缓存版本 + def fetch_with_cache(unionid) + cache_key = "dingtalk_user_#{unionid}" - Discourse.cache.fetch(cache_key, expires_in: 7100) do - log "[缓存] 企业Token缓存未命中,重新获取" - uncached_corp_access_token + Discourse.cache.fetch(cache_key, expires_in: 3600) do + log "[缓存] 用户详情缓存未命中,重新获取" + fetch_corp_details(unionid) end end - def cached_userid_lookup(corp_token, unionid) - cache_key = "dingtalk_unionid_mapping_#{unionid}" - - Discourse.cache.fetch(cache_key, expires_in: 86400) do - log "[缓存] UnionID映射缓存未命中,重新查询" - uncached_userid_lookup(corp_token, unionid) - end + # 无缓存版本 + def fetch_without_cache(unionid) + fetch_corp_details(unionid) end - def cached_user_details(corp_token, userid) - cache_key = "dingtalk_user_details_#{userid}" - - Discourse.cache.fetch(cache_key, expires_in: 3600) do - log "[缓存] 用户详情缓存未命中,重新获取" - uncached_user_details(corp_token, userid) - end - end + # 企业API获取详细信息 + def fetch_corp_details(unionid) + # 获取企业令牌 + corp_token = get_corp_access_token + return log_failure("企业令牌获取失败") unless corp_token - #===== 无缓存方法 ===== - def uncached_corp_access_token - log "[HTTP] 请求企业Token | AppKey: #{SiteSetting.oauth2_client_id}" + # 通过unionid获取userid + userid = get_userid(corp_token, unionid) + return log_failure("UserID查询失败") unless userid - response = Faraday.post( - "https://api.dingtalk.com/v1.0/oauth2/accessToken", # 最新API地址 - { - appKey: SiteSetting.oauth2_client_id, - appSecret: SiteSetting.oauth2_client_secret - }.to_json, + # 获取完整用户信息 + response = Faraday.get( + "https://api.dingtalk.com/v1.0/contact/users/#{userid}", + nil, { - "Content-Type" => "application/json", - "Accept" => "application/json" + "x-acs-dingtalk-access-token" => corp_token } ) - log_response(response, "企业Token") - JSON.parse(response.body)["accessToken"] rescue nil + return log_failure("用户详情请求失败") unless response.success? + + parse_details(JSON.parse(response.body)) end - def uncached_userid_lookup(corp_token, unionid) - log "[HTTP] 查询UserID | UnionID: #{unionid}" + # 获取企业访问令牌(带缓存) + def get_corp_access_token + cache_key = "dingtalk_corp_token_#{SiteSetting.oauth2_client_id}" + Discourse.cache.fetch(cache_key, expires_in: 7100) do + log "[缓存] 企业令牌缓存未命中,重新获取" + + response = Faraday.post( + "https://api.dingtalk.com/v1.0/oauth2/accessToken", + { + appKey: SiteSetting.oauth2_client_id, + appSecret: SiteSetting.oauth2_client_secret + }.to_json, + { + "Content-Type" => "application/json", + "Accept" => "application/json" + } + ) + + JSON.parse(response.body)["accessToken"] rescue nil + end + end + + # 通过unionid获取userid + def get_userid(corp_token, unionid) response = Faraday.post( - "https://api.dingtalk.com/v1.0/contact/users/unionId/get", # 最新API地址 + "https://api.dingtalk.com/v1.0/contact/users/unionId/get", { unionId: unionid }.to_json, { "Content-Type" => "application/json", @@ -111,57 +149,47 @@ def uncached_userid_lookup(corp_token, unionid) } ) - log_response(response, "UserID查询") - JSON.parse(response.body)["userId"] rescue nil - end - - def uncached_user_details(corp_token, userid) - log "[HTTP] 获取用户详情 | UserID: #{userid}" - - response = Faraday.get( - "https://api.dingtalk.com/v1.0/contact/users/#{userid}", # 最新API地址 - nil, - { - "x-acs-dingtalk-access-token" => corp_token - } - ) - - log_response(response, "用户详情") - JSON.parse(response.body) rescue nil + JSON.parse(response.body).dig("result", "userId") rescue nil end - #===== 通用方法 ===== + # 解析用户详情 def parse_details(data) { - user_id: data.dig("unionId"), - username: data.dig("nick"), - name: data.dig("name"), - email: data.dig("email") || fallback_email(data), - avatar: data.dig("avatarUrl") - }.tap do |result| - result[:email_verified] = email_verified?(result) - end - end - - def fallback_email(data) - "#{data['unionId']}@#{SiteSetting.oauth2_email_domain}" + user_id: data["unionId"], + username: data["nick"] || data["name"], + name: data["name"], + email: data["email"] || "#{data['unionId']}@#{SiteSetting.oauth2_email_domain}", + avatar: data["avatarUrl"], + email_verified: data["email"].present? + } end - def email_verified?(details) - details[:email].present? && details[:email].include?("@") + # 构建认证结果 + def build_auth_result(details) + Auth::Result.new.tap do |result| + result.email = details[:email] + result.email_valid = details[:email_verified] + result.username = details[:username] + result.name = details[:name] + result.extra_data = { unionid: details[:user_id] } + end end + # 统一日志方法 def log_response(response, api_name) log <<~LOG - [HTTP响应] #{api_name} API + [API响应] #{api_name} 状态码: #{response.status} - 响应头: #{response.headers.to_h.to_json} - 响应体: #{response.body.inspect} + 响应头: #{response.headers.to_json} + 响应体: #{response.body[0..200]}... LOG end - def log_failure(message) - log "[错误] #{message}" - nil + def auth_failed(reason) + log "[错误] 认证失败: #{reason}" + result = Auth::Result.new + result.failed = true + result.failed_reason = reason + result end end \ No newline at end of file From 1b3333d18ec49480a1f3dfbc844602c375606d8f Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 14:28:11 +0800 Subject: [PATCH 04/35] oauth2_scope --- config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/settings.yml b/config/settings.yml index 7bdad09..b9cac4b 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -43,7 +43,7 @@ login: oauth2_authorize_options: default: "scope" type: list - scope: "" + oauth2_scope: "" oauth2_json_unionid_path: "unionid" oauth2_button_title: default: "with OAuth2" From 7dbd601e6c7dab7046e547371eafea955ea64d8d Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 14:50:29 +0800 Subject: [PATCH 05/35] =?UTF-8?q?register=5Fmiddleware=E4=B8=BB=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=8F=82=E6=95=B0=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/oauth2_basic_authenticator.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/oauth2_basic_authenticator.rb b/lib/oauth2_basic_authenticator.rb index 0b77def..ac7eacb 100644 --- a/lib/oauth2_basic_authenticator.rb +++ b/lib/oauth2_basic_authenticator.rb @@ -19,13 +19,16 @@ def register_middleware(omniauth) setup: lambda { |env| opts = env["omniauth.strategy"].options - opts[:client_id] = SiteSetting.oauth2_client_id - opts[:client_secret] = SiteSetting.oauth2_client_secret + opts[:clientId] = SiteSetting.oauth2_client_id + opts[:clientSecret] = SiteSetting.oauth2_client_secret opts[:provider_ignores_state] = SiteSetting.oauth2_disable_csrf opts[:client_options] = { authorize_url: SiteSetting.oauth2_authorize_url, token_url: SiteSetting.oauth2_token_url, token_method: SiteSetting.oauth2_token_url_method.downcase.to_sym, + authorize_params: { + prompt: "consent" # 强制要求用户授权时显示权限确认界面 + } } opts[:authorize_options] = SiteSetting .oauth2_authorize_options From 026c4f9c7f7986b1031e127ba5c98e58a4fb6bb4 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:05:45 +0800 Subject: [PATCH 06/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/{DingtalkAuthenticator.rb => dingtalk_authenticator.rb} | 0 lib/oauth2_basic_authenticator.rb | 4 ++-- plugin.rb | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) rename lib/{DingtalkAuthenticator.rb => dingtalk_authenticator.rb} (100%) diff --git a/lib/DingtalkAuthenticator.rb b/lib/dingtalk_authenticator.rb similarity index 100% rename from lib/DingtalkAuthenticator.rb rename to lib/dingtalk_authenticator.rb diff --git a/lib/oauth2_basic_authenticator.rb b/lib/oauth2_basic_authenticator.rb index ac7eacb..96ca77f 100644 --- a/lib/oauth2_basic_authenticator.rb +++ b/lib/oauth2_basic_authenticator.rb @@ -19,8 +19,8 @@ def register_middleware(omniauth) setup: lambda { |env| opts = env["omniauth.strategy"].options - opts[:clientId] = SiteSetting.oauth2_client_id - opts[:clientSecret] = SiteSetting.oauth2_client_secret + opts[:client_id] = SiteSetting.oauth2_client_id + opts[:client_secret] = SiteSetting.oauth2_client_secret opts[:provider_ignores_state] = SiteSetting.oauth2_disable_csrf opts[:client_options] = { authorize_url: SiteSetting.oauth2_authorize_url, diff --git a/plugin.rb b/plugin.rb index e0e2d31..df53207 100644 --- a/plugin.rb +++ b/plugin.rb @@ -32,3 +32,7 @@ auth_provider title_setting: "oauth2_button_title", authenticator: OAuth2BasicAuthenticator.new require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator" + +require_relative "lib/validators/oauth2_basic/dingtalk_authenticator" + +auth_provider authenticator: DingtalkAuthenticator.new From a8ca50090c2cc6d2ed9acea70566222ba829c128 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:22:56 +0800 Subject: [PATCH 07/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin.rb b/plugin.rb index df53207..fd0e119 100644 --- a/plugin.rb +++ b/plugin.rb @@ -33,6 +33,5 @@ require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator" -require_relative "lib/validators/oauth2_basic/dingtalk_authenticator" -auth_provider authenticator: DingtalkAuthenticator.new +auth_ding_provider authenticator: DingtalkAuthenticator.new From 59ccd0a1bb1a05e31840adc06a658c0d773b4fcf Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:27:18 +0800 Subject: [PATCH 08/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index fd0e119..1320e30 100644 --- a/plugin.rb +++ b/plugin.rb @@ -12,6 +12,7 @@ require_relative "lib/omniauth/strategies/oauth2_basic" require_relative "lib/oauth2_faraday_formatter" require_relative "lib/oauth2_basic_authenticator" +require_relative "lib/dingtalk_authenticator.rb" # You should use this register if you want to add custom paths to traverse the user details JSON. # We'll store the value in the user associated account's extra attribute hash using the full path as the key. @@ -33,5 +34,4 @@ require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator" - auth_ding_provider authenticator: DingtalkAuthenticator.new From ed3ee3779f01e36ccd61d5fbc5780277444cb797 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:31:19 +0800 Subject: [PATCH 09/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 1320e30..5527ed1 100644 --- a/plugin.rb +++ b/plugin.rb @@ -34,4 +34,4 @@ require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator" -auth_ding_provider authenticator: DingtalkAuthenticator.new +auth_provider authenticator: DingtalkAuthenticator.new From da95c407f73b45ffc9e2669566ba9dcbc6167cc0 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:36:31 +0800 Subject: [PATCH 10/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/server.zh_CN.yml | 1 + config/settings.yml | 3 +++ plugin.rb | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml index 869def8..dbc3298 100644 --- a/config/locales/server.zh_CN.yml +++ b/config/locales/server.zh_CN.yml @@ -34,6 +34,7 @@ zh_CN: oauth2_authorize_options: "授权时请求这些选项" oauth2_scope: "授权请求此范围时" oauth2_button_title: "OAuth2 按钮的文本" + oauth2_dingtalk_title: "dingtalk 按钮的文本" oauth2_allow_association_change: 允许用户从 OAuth2 提供商断开并重新连接他们的 Discourse 帐户 oauth2_disable_csrf: "禁用 CSRF 检查" errors: diff --git a/config/settings.yml b/config/settings.yml index b9cac4b..27292d0 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -48,6 +48,9 @@ login: oauth2_button_title: default: "with OAuth2" client: true + oauth2_dingtalk_title: + default: "with DingTalk" + client: true oauth2_allow_association_change: default: false oauth2_disable_csrf: diff --git a/plugin.rb b/plugin.rb index 5527ed1..38f34e2 100644 --- a/plugin.rb +++ b/plugin.rb @@ -34,4 +34,6 @@ require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator" -auth_provider authenticator: DingtalkAuthenticator.new + +auth_provider title_setting: "oauth2_dingtalk_title", authenticator: DingtalkAuthenticator.new + From 29e47cf7c67276a95bb330a730a21f36d8457a2f Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:44:23 +0800 Subject: [PATCH 11/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 38f34e2..0211a57 100644 --- a/plugin.rb +++ b/plugin.rb @@ -30,7 +30,7 @@ # }, self) DiscoursePluginRegistry.define_filtered_register :oauth2_basic_required_json_paths -auth_provider title_setting: "oauth2_button_title", authenticator: OAuth2BasicAuthenticator.new +# auth_provider title_setting: "oauth2_button_title", authenticator: OAuth2BasicAuthenticator.new require_relative "lib/validators/oauth2_basic/oauth2_fetch_user_details_validator" From 577521249ddd492d87e203b90393f12311f221ff Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 15:58:20 +0800 Subject: [PATCH 12/35] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E9=92=89=E9=92=89?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=99=A8=20dingtalk=5Fauthenticator.rb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 64073dc..5027a3b 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -3,7 +3,30 @@ class DingtalkAuthenticator < OAuth2BasicAuthenticator def name "dingtalk" end - + def register_middleware(omniauth) + omniauth.provider :oauth2_basic, + name: name, + setup: lambda { |env| + opts = env["omniauth.strategy"].options + # 钉钉专用配置 + opts[:client_options] = { + token_url: "https://api.dingtalk.com/v1.0/oauth2/userAccessToken", + auth_scheme: :request_body + } + opts[:token_params] = { + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json" + }, + body: { + clientId: SiteSetting.oauth2_client_id, + clientSecret: SiteSetting.oauth2_client_secret, + code: env["rack.request.query_hash"]["code"], + grantType: "authorization_code" + }.to_json + } + } + end # 核心认证流程 def after_authenticate(auth, existing_account: nil) log "[钉钉] 开始认证流程" From f5c14e74d6fb10f3cf57ca29ef24c59be469dadd Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 16:07:13 +0800 Subject: [PATCH 13/35] =?UTF-8?q?=E8=AE=A4=E8=AF=81=E8=B5=B7=E7=82=B9?= =?UTF-8?q?=EF=BC=8C=E4=BC=A0=E5=8F=82=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 25 +------------------------ lib/oauth2_basic_authenticator.rb | 9 ++++++++- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 5027a3b..64073dc 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -3,30 +3,7 @@ class DingtalkAuthenticator < OAuth2BasicAuthenticator def name "dingtalk" end - def register_middleware(omniauth) - omniauth.provider :oauth2_basic, - name: name, - setup: lambda { |env| - opts = env["omniauth.strategy"].options - # 钉钉专用配置 - opts[:client_options] = { - token_url: "https://api.dingtalk.com/v1.0/oauth2/userAccessToken", - auth_scheme: :request_body - } - opts[:token_params] = { - headers: { - "Content-Type" => "application/json", - "Accept" => "application/json" - }, - body: { - clientId: SiteSetting.oauth2_client_id, - clientSecret: SiteSetting.oauth2_client_secret, - code: env["rack.request.query_hash"]["code"], - grantType: "authorization_code" - }.to_json - } - } - end + # 核心认证流程 def after_authenticate(auth, existing_account: nil) log "[钉钉] 开始认证流程" diff --git a/lib/oauth2_basic_authenticator.rb b/lib/oauth2_basic_authenticator.rb index 96ca77f..5f03671 100644 --- a/lib/oauth2_basic_authenticator.rb +++ b/lib/oauth2_basic_authenticator.rb @@ -50,8 +50,15 @@ def register_middleware(omniauth) opts[:client_options][:auth_scheme] = :request_body opts[:token_params] = { headers: { - "Authorization" => basic_auth_header, + "Content-Type" => "application/json", + "Accept" => "application/json" }, + body: { + clientId: opts[:client_id], + clientSecret: opts[:client_secret], + code: env["rack.request.query_hash"]["code"], + grantType: "authorization_code" + }.to_json } elsif SiteSetting.oauth2_send_auth_header? opts[:client_options][:auth_scheme] = :basic_auth From 6a6b6e840d4be5c4c4f148ab9d6fe334f783dadd Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 16:39:04 +0800 Subject: [PATCH 14/35] =?UTF-8?q?=E8=AE=A4=E8=AF=81=E8=B5=B7=E7=82=B9?= =?UTF-8?q?=EF=BC=8C=E4=BC=A0=E5=8F=82=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/oauth2_basic_authenticator.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/oauth2_basic_authenticator.rb b/lib/oauth2_basic_authenticator.rb index 5f03671..da655b2 100644 --- a/lib/oauth2_basic_authenticator.rb +++ b/lib/oauth2_basic_authenticator.rb @@ -70,6 +70,19 @@ def register_middleware(omniauth) opts[:scope] = SiteSetting.oauth2_scope end + opts[:token_params] = { + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json" + }, + body: { + clientId: opts[:client_id], + clientSecret: opts[:client_secret], + code: env["rack.request.query_hash"]["code"], + grantType: "authorization_code" + }.to_json + } + opts[:client_options][:connection_build] = lambda do |builder| if SiteSetting.oauth2_debug_auth && defined?(OAuth2FaradayFormatter) builder.response :logger, From 242563fe7341f78168e775f5e07d41ec97fc345a Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 16:54:45 +0800 Subject: [PATCH 15/35] =?UTF-8?q?=E8=AE=A4=E8=AF=81=E8=B5=B7=E7=82=B9?= =?UTF-8?q?=EF=BC=8C=E4=BC=A0=E5=8F=82=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 30 ++++++++++++++++++++++++++++++ lib/oauth2_basic_authenticator.rb | 13 ------------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 64073dc..6da081c 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -4,6 +4,36 @@ def name "dingtalk" end + # 重要:继承时保留父类配置 + def register_middleware(omniauth) + super # 先执行父类基础配置 + + omniauth.provider :oauth2_basic, + name: name, + setup: lambda { |env| + opts = env["omniauth.strategy"].options + + # 覆盖钉钉专用配置 + opts[:client_options] = { + site: "https://api.dingtalk.com", + token_url: "/v1.0/oauth2/userAccessToken", + auth_scheme: :request_body + } + + opts[:token_params] = { + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json" + }, + body: { + clientId: SiteSetting.dingtalk_app_key, + clientSecret: SiteSetting.dingtalk_app_secret, + code: env["rack.request.query_hash"]["code"], + grantType: "authorization_code" + }.to_json + } + } + end # 核心认证流程 def after_authenticate(auth, existing_account: nil) log "[钉钉] 开始认证流程" diff --git a/lib/oauth2_basic_authenticator.rb b/lib/oauth2_basic_authenticator.rb index da655b2..5f03671 100644 --- a/lib/oauth2_basic_authenticator.rb +++ b/lib/oauth2_basic_authenticator.rb @@ -70,19 +70,6 @@ def register_middleware(omniauth) opts[:scope] = SiteSetting.oauth2_scope end - opts[:token_params] = { - headers: { - "Content-Type" => "application/json", - "Accept" => "application/json" - }, - body: { - clientId: opts[:client_id], - clientSecret: opts[:client_secret], - code: env["rack.request.query_hash"]["code"], - grantType: "authorization_code" - }.to_json - } - opts[:client_options][:connection_build] = lambda do |builder| if SiteSetting.oauth2_debug_auth && defined?(OAuth2FaradayFormatter) builder.response :logger, From 735c7695f27fef4912d1be655b26eabf5f281ad1 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 17:08:06 +0800 Subject: [PATCH 16/35] =?UTF-8?q?=E7=AD=96=E7=95=A5=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/omniauth/strategies/oauth2_basic.rb | 84 +++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/lib/omniauth/strategies/oauth2_basic.rb b/lib/omniauth/strategies/oauth2_basic.rb index d493c8a..5b0d7b6 100644 --- a/lib/omniauth/strategies/oauth2_basic.rb +++ b/lib/omniauth/strategies/oauth2_basic.rb @@ -3,6 +3,90 @@ class OmniAuth::Strategies::Oauth2Basic < ::OmniAuth::Strategies::OAuth2 option :name, "oauth2_basic" + # 禁用默认参数编码 + option :token_params, { + parse: :json + } + + # 核心方法:钉钉Token获取 + def build_access_token + verifier = request.params['code'] + + # 构造钉钉专用请求体 + raw_body = { + clientId: client.id, + clientSecret: client.secret, + code: verifier, + grantType: "authorization_code" + }.to_json + + # 发送自定义JSON请求 + response = client.request(:post, client.token_url, body: raw_body) do |req| + req.headers.update( + "Content-Type" => "application/json", + "Accept" => "application/json" + ) + end + + # 手动解析钉钉响应并转换为标准OAuth2格式 + token_data = JSON.parse(response.body).deep_symbolize_keys + ::OAuth2::AccessToken.from_hash( + client, + { + access_token: token_data[:accessToken], # 钉钉 -> 标准 + refresh_token: token_data[:refreshToken], # 钉钉 -> 标准 + expires_in: token_data[:expireIn], # 钉钉 -> 标准 + token_type: "Bearer" + } + ) + end + + # 用户ID映射(使用unionId) + uid do + raw_info[:unionId] || raw_info["unionId"] || raw_info[:openId] || "unknown" + end + + # 用户信息映射 + info do + { + name: raw_info[:nick] || raw_info["nick"] || "钉钉用户", + email: raw_info[:email] || raw_info["email"] || "#{raw_info[:unionId]}@dingtalk.fallback", + image: raw_info[:avatarUrl] || raw_info["avatarUrl"] + } + end + + # 获取钉钉用户信息(修复请求头) + def raw_info + @raw_info ||= begin + + user_info_url = SiteSetting.oauth2_user_json_url + + conn = Faraday.new( + url: user_info_url, + headers: { + 'x-acs-dingtalk-access-token' => access_token.token, + 'x-acs-dingtalk-org-id' => SiteSetting.oauth2_client_corpid, + 'Accept' => 'application/json' + } + ) do |f| + f.request :json + f.response :json + end + + # 添加调试日志 + Rails.logger.info "用户信息接口URL: #{user_info_url}" + Rails.logger.info "请求头: #{conn.headers}" + + response = conn.get("") + raise StandardError, response.body['message'] if response.status != 200 + + response.body.deep_symbolize_keys + rescue => e + Rails.logger.error "钉钉用户信息获取失败: #{e.message}" + {} + end + end + uid do if path = SiteSetting.oauth2_callback_user_id_path.split(".") recurse(access_token, [*path]) if path.present? From 4f316609200fe05be9f745cfafbb60e237530460 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 17:18:46 +0800 Subject: [PATCH 17/35] =?UTF-8?q?=E7=AD=96=E7=95=A5=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 6da081c..6e574ab 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -26,8 +26,8 @@ def register_middleware(omniauth) "Accept" => "application/json" }, body: { - clientId: SiteSetting.dingtalk_app_key, - clientSecret: SiteSetting.dingtalk_app_secret, + clientId: SiteSetting.oauth2_client_id, + clientSecret: SiteSetting.oauth2_client_secret, code: env["rack.request.query_hash"]["code"], grantType: "authorization_code" }.to_json From a6b135ecb3793773a27fa07be3523c4e25ade9b2 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 18:35:36 +0800 Subject: [PATCH 18/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/settings.yml b/config/settings.yml index 27292d0..dde4b61 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -55,7 +55,7 @@ login: default: false oauth2_disable_csrf: default: false - hidden: true + hidden: false oauth2_email_domain: default: "tmp.renogy.com" client: true From 05c7e897a8ed47f56a21700654e55d1121a5bad9 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 18:49:29 +0800 Subject: [PATCH 19/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 15 +++++------ lib/omniauth/strategies/oauth2_basic.rb | 34 ++++++++++++------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 6e574ab..dfee9f4 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -4,30 +4,27 @@ def name "dingtalk" end - # 重要:继承时保留父类配置 - def register_middleware(omniauth) - super # 先执行父类基础配置 + def register_middleware(omniauth) omniauth.provider :oauth2_basic, name: name, setup: lambda { |env| opts = env["omniauth.strategy"].options - # 覆盖钉钉专用配置 + # 强制使用钉钉参数命名规范 opts[:client_options] = { - site: "https://api.dingtalk.com", - token_url: "/v1.0/oauth2/userAccessToken", + token_url: "https://api.dingtalk.com/v1.0/oauth2/userAccessToken", auth_scheme: :request_body } opts[:token_params] = { headers: { "Content-Type" => "application/json", - "Accept" => "application/json" + "X-Dingtalk-Isv" => "true" # 企业应用必须的头部 }, body: { - clientId: SiteSetting.oauth2_client_id, - clientSecret: SiteSetting.oauth2_client_secret, + clientId: SiteSetting.dingtalk_app_key, + clientSecret: SiteSetting.dingtalk_app_secret, code: env["rack.request.query_hash"]["code"], grantType: "authorization_code" }.to_json diff --git a/lib/omniauth/strategies/oauth2_basic.rb b/lib/omniauth/strategies/oauth2_basic.rb index 5b0d7b6..fb5e66c 100644 --- a/lib/omniauth/strategies/oauth2_basic.rb +++ b/lib/omniauth/strategies/oauth2_basic.rb @@ -8,36 +8,36 @@ class OmniAuth::Strategies::Oauth2Basic < ::OmniAuth::Strategies::OAuth2 parse: :json } - # 核心方法:钉钉Token获取 def build_access_token verifier = request.params['code'] - # 构造钉钉专用请求体 + # 钉钉要求参数命名规范(驼峰式) raw_body = { - clientId: client.id, + clientId: client.id, # 注意首字母大写 clientSecret: client.secret, code: verifier, grantType: "authorization_code" }.to_json - # 发送自定义JSON请求 - response = client.request(:post, client.token_url, body: raw_body) do |req| - req.headers.update( + # 强制JSON请求头和格式 + response = client.request(:post, client.token_url, { + body: raw_body, + headers: { "Content-Type" => "application/json", "Accept" => "application/json" - ) - end + } + }) - # 手动解析钉钉响应并转换为标准OAuth2格式 - token_data = JSON.parse(response.body).deep_symbolize_keys - ::OAuth2::AccessToken.from_hash( + # 调试日志(打印实际发送内容) + Rails.logger.info "钉钉Token请求体: #{raw_body}" + Rails.logger.info "钉钉Token响应: #{response.body}" + + # 解析钉钉响应 + token_data = JSON.parse(response.body) + ::OAuth2::AccessToken.new( client, - { - access_token: token_data[:accessToken], # 钉钉 -> 标准 - refresh_token: token_data[:refreshToken], # 钉钉 -> 标准 - expires_in: token_data[:expireIn], # 钉钉 -> 标准 - token_type: "Bearer" - } + token_data['accessToken'], + expires_in: token_data['expireIn'] ) end From 5ae724b86ae0f18b0acce2057a7f15e3618247f4 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 18:56:58 +0800 Subject: [PATCH 20/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index dfee9f4..80b3818 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -23,8 +23,8 @@ def register_middleware(omniauth) "X-Dingtalk-Isv" => "true" # 企业应用必须的头部 }, body: { - clientId: SiteSetting.dingtalk_app_key, - clientSecret: SiteSetting.dingtalk_app_secret, + clientId: SiteSetting.oauth2_client_id, + clientSecret: SiteSetting.oauth2_client_secret, code: env["rack.request.query_hash"]["code"], grantType: "authorization_code" }.to_json From 35707936f3a8763c824f068368ee1c18b2692199 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Mon, 28 Apr 2025 19:16:15 +0800 Subject: [PATCH 21/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 38 ++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 80b3818..7182af2 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -11,21 +11,26 @@ def register_middleware(omniauth) setup: lambda { |env| opts = env["omniauth.strategy"].options - # 强制使用钉钉参数命名规范 + # 强制使用钉钉参数命名 + opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 + opts[:client_secret] = SiteSetting.oauth2_client_secret + opts[:client_options] = { - token_url: "https://api.dingtalk.com/v1.0/oauth2/userAccessToken", + site: "https://api.dingtalk.com", + authorize_url: "/oauth2/auth", + token_url: "/v1.0/oauth2/userAccessToken", auth_scheme: :request_body } opts[:token_params] = { headers: { "Content-Type" => "application/json", - "X-Dingtalk-Isv" => "true" # 企业应用必须的头部 + "X-Dingtalk-Isv" => "true" }, body: { - clientId: SiteSetting.oauth2_client_id, - clientSecret: SiteSetting.oauth2_client_secret, - code: env["rack.request.query_hash"]["code"], + clientId: opts[:client_id], # 使用驼峰命名 + clientSecret: opts[:client_secret], + code: env.dig("rack.request.query_hash", "code"), # 安全访问 grantType: "authorization_code" }.to_json } @@ -35,6 +40,9 @@ def register_middleware(omniauth) def after_authenticate(auth, existing_account: nil) log "[钉钉] 开始认证流程" + # 确保 auth 参数存在且包含 code + return auth_failed("无效的认证参数") unless auth && auth[:code] + # 1. 通过授权码获取用户访问令牌 user_token = get_user_access_token(auth[:code]) return auth_failed("用户令牌获取失败") unless user_token @@ -82,15 +90,16 @@ def get_user_access_token(code) def get_base_user_info(user_token) log "[HTTP] 获取基础用户信息" + return nil unless user_token + response = Faraday.get( "https://api.dingtalk.com/v1.0/contact/users/me", nil, - { - "x-acs-dingtalk-access-token" => user_token - } + { "x-acs-dingtalk-access-token" => user_token } ) - log_response(response, "基础信息") + return log_failure("请求失败: #{response.status}") unless response.success? + JSON.parse(response.body) rescue nil end @@ -181,14 +190,15 @@ def get_userid(corp_token, unionid) # 解析用户详情 def parse_details(data) + return {} unless data.is_a?(Hash) + { - user_id: data["unionId"], - username: data["nick"] || data["name"], - name: data["name"], + user_id: data.dig("unionId"), + username: data["nick"] || data["name"] || "钉钉用户", email: data["email"] || "#{data['unionId']}@#{SiteSetting.oauth2_email_domain}", avatar: data["avatarUrl"], email_verified: data["email"].present? - } + }.compact end # 构建认证结果 From 0ec5256629813b012681a8f03b9ac0ce4983ea7e Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 09:55:03 +0800 Subject: [PATCH 22/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 7182af2..3de4779 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -6,6 +6,7 @@ def name def register_middleware(omniauth) + super omniauth.provider :oauth2_basic, name: name, setup: lambda { |env| From e6c52476d16f1d716a0c0f95cc4b007a7f829107 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 10:19:25 +0800 Subject: [PATCH 23/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 66 +++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 3de4779..4332f40 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -12,41 +12,47 @@ def register_middleware(omniauth) setup: lambda { |env| opts = env["omniauth.strategy"].options + # 强制启用 Faraday 日志中间件 + opts[:client_options][:connection_build] = lambda do |builder| + if SiteSetting.oauth2_debug_auth + builder.response :logger, Rails.logger, { + bodies: true, + formatter: OAuth2FaradayFormatter # 使用自定义格式化类 + } + end + builder.adapter FinalDestination::FaradayAdapter + end + # 强制使用钉钉参数命名 - opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 - opts[:client_secret] = SiteSetting.oauth2_client_secret - - opts[:client_options] = { - site: "https://api.dingtalk.com", - authorize_url: "/oauth2/auth", - token_url: "/v1.0/oauth2/userAccessToken", - auth_scheme: :request_body - } - - opts[:token_params] = { - headers: { - "Content-Type" => "application/json", - "X-Dingtalk-Isv" => "true" - }, - body: { - clientId: opts[:client_id], # 使用驼峰命名 - clientSecret: opts[:client_secret], - code: env.dig("rack.request.query_hash", "code"), # 安全访问 - grantType: "authorization_code" - }.to_json - } + # opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 + # opts[:client_secret] = SiteSetting.oauth2_client_secret + # + # opts[:client_options] = { + # site: "https://api.dingtalk.com", + # authorize_url: "/oauth2/auth", + # token_url: "/v1.0/oauth2/userAccessToken", + # auth_scheme: :request_body + # } + # + # opts[:token_params] = { + # headers: { + # "Content-Type" => "application/json", + # "X-Dingtalk-Isv" => "true" + # }, + # body: { + # clientId: opts[:client_id], # 使用驼峰命名 + # clientSecret: opts[:client_secret], + # code: env.dig("rack.request.query_hash", "code"), # 安全访问 + # grantType: "authorization_code" + # }.to_json + # } } end # 核心认证流程 def after_authenticate(auth, existing_account: nil) - log "[钉钉] 开始认证流程" - - # 确保 auth 参数存在且包含 code - return auth_failed("无效的认证参数") unless auth && auth[:code] - - # 1. 通过授权码获取用户访问令牌 - user_token = get_user_access_token(auth[:code]) - return auth_failed("用户令牌获取失败") unless user_token + # 直接使用策略类获取的 token + user_token = auth.dig(:credentials, :token) + return auth_failed("Token缺失") unless user_token # 2. 获取基础用户信息(包含unionId) base_info = get_base_user_info(user_token) From 7a14b711bd82e23259e0f1d7914eb2b1561c89b9 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 10:32:33 +0800 Subject: [PATCH 24/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 67 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 4332f40..552f800 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -1,8 +1,5 @@ # dingtalk_authenticator.rb class DingtalkAuthenticator < OAuth2BasicAuthenticator - def name - "dingtalk" - end def register_middleware(omniauth) @@ -12,40 +9,40 @@ def register_middleware(omniauth) setup: lambda { |env| opts = env["omniauth.strategy"].options - # 强制启用 Faraday 日志中间件 - opts[:client_options][:connection_build] = lambda do |builder| - if SiteSetting.oauth2_debug_auth - builder.response :logger, Rails.logger, { - bodies: true, - formatter: OAuth2FaradayFormatter # 使用自定义格式化类 - } - end - builder.adapter FinalDestination::FaradayAdapter - end + # # 强制启用 Faraday 日志中间件 + # opts[:client_options][:connection_build] = lambda do |builder| + # if SiteSetting.oauth2_debug_auth + # builder.response :logger, Rails.logger, { + # bodies: true, + # formatter: OAuth2FaradayFormatter # 使用自定义格式化类 + # } + # end + # builder.adapter FinalDestination::FaradayAdapter + # end # 强制使用钉钉参数命名 - # opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 - # opts[:client_secret] = SiteSetting.oauth2_client_secret - # - # opts[:client_options] = { - # site: "https://api.dingtalk.com", - # authorize_url: "/oauth2/auth", - # token_url: "/v1.0/oauth2/userAccessToken", - # auth_scheme: :request_body - # } - # - # opts[:token_params] = { - # headers: { - # "Content-Type" => "application/json", - # "X-Dingtalk-Isv" => "true" - # }, - # body: { - # clientId: opts[:client_id], # 使用驼峰命名 - # clientSecret: opts[:client_secret], - # code: env.dig("rack.request.query_hash", "code"), # 安全访问 - # grantType: "authorization_code" - # }.to_json - # } + opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 + opts[:client_secret] = SiteSetting.oauth2_client_secret + + opts[:client_options] = { + site: "https://api.dingtalk.com", + authorize_url: "/oauth2/auth", + token_url: "/v1.0/oauth2/userAccessToken", + auth_scheme: :request_body + } + + opts[:token_params] = { + headers: { + "Content-Type" => "application/json", + "X-Dingtalk-Isv" => "true" + }, + body: { + clientId: opts[:client_id], # 使用驼峰命名 + clientSecret: opts[:client_secret], + code: env.dig("rack.request.query_hash", "code"), # 安全访问 + grantType: "authorization_code" + }.to_json + } } end # 核心认证流程 From 18a258d302164f4b8db73111a54192b26ea49b9f Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 10:32:51 +0800 Subject: [PATCH 25/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 552f800..8ad8535 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -3,7 +3,6 @@ class DingtalkAuthenticator < OAuth2BasicAuthenticator def register_middleware(omniauth) - super omniauth.provider :oauth2_basic, name: name, setup: lambda { |env| From d2356a86e9df468cfd7337ef19284d831f2f6d99 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 10:34:48 +0800 Subject: [PATCH 26/35] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 84 +++++++++++++++++------------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 8ad8535..9ad770d 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -2,48 +2,48 @@ class DingtalkAuthenticator < OAuth2BasicAuthenticator - def register_middleware(omniauth) - omniauth.provider :oauth2_basic, - name: name, - setup: lambda { |env| - opts = env["omniauth.strategy"].options - - # # 强制启用 Faraday 日志中间件 - # opts[:client_options][:connection_build] = lambda do |builder| - # if SiteSetting.oauth2_debug_auth - # builder.response :logger, Rails.logger, { - # bodies: true, - # formatter: OAuth2FaradayFormatter # 使用自定义格式化类 - # } - # end - # builder.adapter FinalDestination::FaradayAdapter - # end - - # 强制使用钉钉参数命名 - opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 - opts[:client_secret] = SiteSetting.oauth2_client_secret - - opts[:client_options] = { - site: "https://api.dingtalk.com", - authorize_url: "/oauth2/auth", - token_url: "/v1.0/oauth2/userAccessToken", - auth_scheme: :request_body - } - - opts[:token_params] = { - headers: { - "Content-Type" => "application/json", - "X-Dingtalk-Isv" => "true" - }, - body: { - clientId: opts[:client_id], # 使用驼峰命名 - clientSecret: opts[:client_secret], - code: env.dig("rack.request.query_hash", "code"), # 安全访问 - grantType: "authorization_code" - }.to_json - } - } - end + # def register_middleware(omniauth) + # omniauth.provider :oauth2_basic, + # name: name, + # setup: lambda { |env| + # opts = env["omniauth.strategy"].options + # + # # # 强制启用 Faraday 日志中间件 + # # opts[:client_options][:connection_build] = lambda do |builder| + # # if SiteSetting.oauth2_debug_auth + # # builder.response :logger, Rails.logger, { + # # bodies: true, + # # formatter: OAuth2FaradayFormatter # 使用自定义格式化类 + # # } + # # end + # # builder.adapter FinalDestination::FaradayAdapter + # # end + # + # # 强制使用钉钉参数命名 + # opts[:client_id] = SiteSetting.oauth2_client_id # 使用独立配置项 + # opts[:client_secret] = SiteSetting.oauth2_client_secret + # + # opts[:client_options] = { + # site: "https://api.dingtalk.com", + # authorize_url: "/oauth2/auth", + # token_url: "/v1.0/oauth2/userAccessToken", + # auth_scheme: :request_body + # } + # + # opts[:token_params] = { + # headers: { + # "Content-Type" => "application/json", + # "X-Dingtalk-Isv" => "true" + # }, + # body: { + # clientId: opts[:client_id], # 使用驼峰命名 + # clientSecret: opts[:client_secret], + # code: env.dig("rack.request.query_hash", "code"), # 安全访问 + # grantType: "authorization_code" + # }.to_json + # } + # } + # end # 核心认证流程 def after_authenticate(auth, existing_account: nil) # 直接使用策略类获取的 token From bf7193b2eb30e377adb3fc10419fc6760e6d0cfc Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 11:00:14 +0800 Subject: [PATCH 27/35] =?UTF-8?q?get=5Fcorp=5Faccess=5Ftoken=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 9ad770d..e4bc62a 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -160,19 +160,17 @@ def get_corp_access_token Discourse.cache.fetch(cache_key, expires_in: 7100) do log "[缓存] 企业令牌缓存未命中,重新获取" - - response = Faraday.post( + response = Faraday.get( "https://api.dingtalk.com/v1.0/oauth2/accessToken", + nil, { - appKey: SiteSetting.oauth2_client_id, - appSecret: SiteSetting.oauth2_client_secret - }.to_json, - { - "Content-Type" => "application/json", - "Accept" => "application/json" + "appKey" => SiteSetting.oauth2_client_id, + "appSecret" => SiteSetting.oauth2_client_secret } ) + # 添加调试日志,输出完整响应 + log_response(response, "企业令牌接口") JSON.parse(response.body)["accessToken"] rescue nil end end From 5930929360e4b5fdcf3ef8a652be13c26b62faac Mon Sep 17 00:00:00 2001 From: jessefeng Date: Tue, 29 Apr 2025 11:12:43 +0800 Subject: [PATCH 28/35] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index e4bc62a..c19a437 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -55,6 +55,7 @@ def after_authenticate(auth, existing_account: nil) return auth_failed("基础信息获取失败") unless base_info unionid = base_info.dig("unionId") + log "[钉钉] after_authenticate: 获取详细用户信息" # 3. 获取详细用户信息 user_details = fetch_user_details(unionid) return auth_failed("用户详情获取失败") unless user_details @@ -132,6 +133,7 @@ def fetch_without_cache(unionid) # 企业API获取详细信息 def fetch_corp_details(unionid) + log "[钉钉] fetch_corp_details" # 获取企业令牌 corp_token = get_corp_access_token return log_failure("企业令牌获取失败") unless corp_token From fd8ee7e70dc6b21cdf68b7a891c259dfbbc76083 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 09:46:26 +0800 Subject: [PATCH 29/35] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=8E=A5=E5=8F=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.yml | 3 +-- lib/dingtalk_authenticator.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config/settings.yml b/config/settings.yml index dde4b61..4a18415 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -60,5 +60,4 @@ login: default: "tmp.renogy.com" client: true dingtalk_enable_cache: - default: false - client: true \ No newline at end of file + default: false \ No newline at end of file diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index c19a437..55b7fbd 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -163,7 +163,7 @@ def get_corp_access_token Discourse.cache.fetch(cache_key, expires_in: 7100) do log "[缓存] 企业令牌缓存未命中,重新获取" response = Faraday.get( - "https://api.dingtalk.com/v1.0/oauth2/accessToken", + "https://oapi.dingtalk.com/gettoken", nil, { "appKey" => SiteSetting.oauth2_client_id, From d7dde76680d9088c20b5a60ed5ffb34f5dbcede7 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 10:46:11 +0800 Subject: [PATCH 30/35] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=8E=A5=E5=8F=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 55b7fbd..0d49d31 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -164,16 +164,15 @@ def get_corp_access_token log "[缓存] 企业令牌缓存未命中,重新获取" response = Faraday.get( "https://oapi.dingtalk.com/gettoken", - nil, { - "appKey" => SiteSetting.oauth2_client_id, - "appSecret" => SiteSetting.oauth2_client_secret - } + appkey: SiteSetting.oauth2_client_id, + appsecret: SiteSetting.oauth2_client_secret + }, + {} ) - # 添加调试日志,输出完整响应 log_response(response, "企业令牌接口") - JSON.parse(response.body)["accessToken"] rescue nil + JSON.parse(response.body)["access_token"] rescue nil # 注意字段名可能是 "access_token" end end From afd26864927b69ea743f5946d61cddbbdc096f3e Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 13:43:12 +0800 Subject: [PATCH 31/35] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=8E=A5=E5=8F=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 0d49d31..d4eeef1 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -178,6 +178,7 @@ def get_corp_access_token # 通过unionid获取userid def get_userid(corp_token, unionid) + log "通过unionid获取userid 开始" response = Faraday.post( "https://api.dingtalk.com/v1.0/contact/users/unionId/get", { unionId: unionid }.to_json, From 52d3324cdcb98e0d6f695e9e9ea717ee48bf13ef Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 14:07:50 +0800 Subject: [PATCH 32/35] =?UTF-8?q?=E8=8E=B7=E5=8F=96userID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index d4eeef1..ab26543 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -143,12 +143,15 @@ def fetch_corp_details(unionid) return log_failure("UserID查询失败") unless userid # 获取完整用户信息 - response = Faraday.get( - "https://api.dingtalk.com/v1.0/contact/users/#{userid}", - nil, + response = Faraday.post( + "https://oapi.dingtalk.com/topapi/v2/user/get", { - "x-acs-dingtalk-access-token" => corp_token + access_token: corp_token + },{ + "Content-Type" => "application/json", + "Accept" => "application/json" } + ) return log_failure("用户详情请求失败") unless response.success? @@ -181,11 +184,12 @@ def get_userid(corp_token, unionid) log "通过unionid获取userid 开始" response = Faraday.post( "https://api.dingtalk.com/v1.0/contact/users/unionId/get", - { unionId: unionid }.to_json, { - "Content-Type" => "application/json", - "x-acs-dingtalk-access-token" => corp_token - } + # "Content-Type" => "application/json", + access_token: corp_token + }, + { unionId: unionid }.to_json, + ) JSON.parse(response.body).dig("result", "userId") rescue nil From b92269bfe9679e39e1c52be9d3f4c2065510136d Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 14:28:02 +0800 Subject: [PATCH 33/35] =?UTF-8?q?=E8=8E=B7=E5=8F=96userID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index ab26543..af91507 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -142,21 +142,24 @@ def fetch_corp_details(unionid) userid = get_userid(corp_token, unionid) return log_failure("UserID查询失败") unless userid - # 获取完整用户信息 + # 获取完整用户信息(钉钉文档要求access_token作为查询参数,请求体包含userid) + url = "https://oapi.dingtalk.com/topapi/v2/user/get?access_token=#{corp_token}" + response = Faraday.post( - "https://oapi.dingtalk.com/topapi/v2/user/get", + url, + { + userid: userid, + language: "zh_CN" # 根据需求添加语言参数 + }.to_json, { - access_token: corp_token - },{ "Content-Type" => "application/json", "Accept" => "application/json" } - ) return log_failure("用户详情请求失败") unless response.success? - parse_details(JSON.parse(response.body)) + parse_details(JSON.parse(response.body).dig("result")) # 注意提取result字段 end # 获取企业访问令牌(带缓存) @@ -182,17 +185,21 @@ def get_corp_access_token # 通过unionid获取userid def get_userid(corp_token, unionid) log "通过unionid获取userid 开始" + + # 钉钉文档要求:access_token作为查询参数,unionid在请求体中 + url = "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=#{corp_token}" + response = Faraday.post( - "https://api.dingtalk.com/v1.0/contact/users/unionId/get", + url, + { unionid: unionid }.to_json, { - # "Content-Type" => "application/json", - access_token: corp_token - }, - { unionId: unionid }.to_json, - + "Content-Type" => "application/json", + "Accept" => "application/json" + } ) - JSON.parse(response.body).dig("result", "userId") rescue nil + # 解析响应 + JSON.parse(response.body).dig("result", "userid") rescue nil end # 解析用户详情 From c6ea7a8945d2c6aee15e07d5cbb99c868e2d5dae Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 14:46:52 +0800 Subject: [PATCH 34/35] =?UTF-8?q?=E8=8E=B7=E5=8F=96userID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index af91507..6bb2b15 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -207,10 +207,10 @@ def parse_details(data) return {} unless data.is_a?(Hash) { - user_id: data.dig("unionId"), - username: data["nick"] || data["name"] || "钉钉用户", - email: data["email"] || "#{data['unionId']}@#{SiteSetting.oauth2_email_domain}", - avatar: data["avatarUrl"], + user_id: data.dig("unionid"), # 修正为小写 + username: data["name"] || "钉钉用户", # 钉钉返回的字段是 name,不是 nick + email: data["email"] || "#{data['unionid']}@#{SiteSetting.oauth2_email_domain}", + avatar: data["avatar"], email_verified: data["email"].present? }.compact end From 98ccb2e028aa285098aa51a630bd9e92b8c33b06 Mon Sep 17 00:00:00 2001 From: jessefeng Date: Wed, 30 Apr 2025 14:58:22 +0800 Subject: [PATCH 35/35] =?UTF-8?q?=E8=8E=B7=E5=8F=96userID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dingtalk_authenticator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dingtalk_authenticator.rb b/lib/dingtalk_authenticator.rb index 6bb2b15..5f00196 100644 --- a/lib/dingtalk_authenticator.rb +++ b/lib/dingtalk_authenticator.rb @@ -207,10 +207,10 @@ def parse_details(data) return {} unless data.is_a?(Hash) { - user_id: data.dig("unionid"), # 修正为小写 - username: data["name"] || "钉钉用户", # 钉钉返回的字段是 name,不是 nick + user_id: data["unionid"] || data["unionId"], # 兼容大小写 + username: data["name"] || "钉钉用户", email: data["email"] || "#{data['unionid']}@#{SiteSetting.oauth2_email_domain}", - avatar: data["avatar"], + avatar: data["avatarUrl"] || data["avatar"], # 钉钉可能返回 avatarUrl email_verified: data["email"].present? }.compact end