Commit 15cb4dfb by Francis Zhou

增加 get_3rd_login_free_user_profile 方法

parent dee88453
...@@ -3,10 +3,35 @@ require 'base64' ...@@ -3,10 +3,35 @@ require 'base64'
require 'openssl' require 'openssl'
require "dingtalk/version" require "dingtalk/version"
require "dingtalk/auth"
require "dingtalk/access_token"
module Dingtalk module Dingtalk
class Error < StandardError class Error < StandardError
end end
class Signature
def initialize(s)
@signature = s
end
def to_s
@signature
end
def url_encoded
URI.encode_www_form_component @signature
end
end
class Request
include Auth
def initialize(app_key:, app_secret:)
@app_key, @app_secret = app_key, app_secret
end
end
class << self class << self
# 个人免登录场景的签名计算 # 个人免登录场景的签名计算
# @url https://ding-doc.dingtalk.com/doc#/faquestions/hxs5v9 # @url https://ding-doc.dingtalk.com/doc#/faquestions/hxs5v9
...@@ -29,12 +54,7 @@ module Dingtalk ...@@ -29,12 +54,7 @@ module Dingtalk
signature_str = OpenSSL::HMAC.digest("SHA256", app_secret, timestamp.to_s) signature_str = OpenSSL::HMAC.digest("SHA256", app_secret, timestamp.to_s)
signature_str_base64 = Base64.encode64(signature_str).strip signature_str_base64 = Base64.encode64(signature_str).strip
if options[:url_encode] Signature.new(signature_str_base64)
# 不知道为什么 URI.encode 方法不行
return URI.encode_www_form_component(signature_str_base64)
end
signature_str_base64
end end
end end
end end
...@@ -6,13 +6,24 @@ module Dingtalk ...@@ -6,13 +6,24 @@ module Dingtalk
class << self class << self
extend Dingtalk::Core extend Dingtalk::Core
# 企业内部应用免登 # 企业内部应用免登录 用户ID获取
# @url https://ding-doc.dingtalk.com/doc#/serverapi2/clotub # @url https://ding-doc.dingtalk.com/doc#/serverapi2/clotub
add_request :get_int_login_free_user_id, :get, Dingtalk::RequestUrl::GET_USER_INFO do |request| add_request :get_int_login_free_user_id, :get, Dingtalk::RequestUrl::INT_LOGIN_FREE_GET_USER_INFO do |request|
request.add_arg :code, required: true, in: :query request.add_arg :code, required: true, in: :query
request.add_arg :access_token, required: true, in: :query request.add_arg :access_token, required: true, in: :query
end end
# 钉钉内免登录第三方网站 个人信息获取
# @url https://ding-doc.dingtalk.com/doc#/serverapi2/etaarr
add_request :get_3rd_login_free_user_profile, :post, Dingtalk::RequestUrl::GET_USER_INFO_SNS do |request|
request.is_json = true
request.add_arg :accessKey, required: true, in: :query
request.add_arg :timestamp, required: true, in: :query
request.add_arg :signature, required: true, in: :query
request.add_arg :tmp_auth_code, required: true, in: :body
end
end end
end end
end end
\ No newline at end of file
...@@ -8,25 +8,43 @@ require "dingtalk" ...@@ -8,25 +8,43 @@ require "dingtalk"
module Dingtalk module Dingtalk
module Core module Core
class RequestBuilder class RequestBuilder
attr_reader :query_args, :payload_args, :required_args attr_reader :query_args, :body_args, :required_args
def initialize def initialize
@is_json = false
@body_args = []
@query_args = [] @query_args = []
@payload_args = []
@required_args = [] @required_args = []
@with_key_and_secret = false @with_key_and_secret = false
end end
def is_json?
@is_json
end
def is_arg_required?(arg_name) def is_arg_required?(arg_name)
@required_args.include? arg_name @required_args.include? arg_name
end end
def has_body_args?
!@body_args.empty?
end
def has_query_args?
!@query_args.empty?
end
def is_json=(b)
@is_json = b
end
def add_arg(arg_name, option) def add_arg(arg_name, option)
case option[:in] case option[:in]
when :body
@body_args << arg_name
when :query when :query
@query_args << arg_name @query_args << arg_name
when :payload
@payload_args << arg_name
else else
raise Dingtalk::Error.new("unknown argument position") raise Dingtalk::Error.new("unknown argument position")
end end
...@@ -40,22 +58,26 @@ module Dingtalk ...@@ -40,22 +58,26 @@ module Dingtalk
yield builder if block_given? yield builder if block_given?
define_method request_name do |method_args = {}| define_method request_name do |method_args = {}|
default_options = { request_options = {}.tap do |h|
query: {}
}
request_options = default_options.tap do |h|
builder.required_args.each do |arg| builder.required_args.each do |arg|
raise Dingtalk::Error.new("missing required argument '#{arg}' when invoke request #{request_name}") \ raise Dingtalk::Error.new("missing required argument '#{arg}' when invoke request #{request_name}") \
if method_args[arg].nil? if method_args[arg].nil?
end end
h[:body] = {} if builder.has_body_args?
h[:query] = {} if builder.has_query_args?
builder.body_args.each do |arg|
h[:body][arg] = method_args[arg]
end
builder.query_args.each do |arg| builder.query_args.each do |arg|
h[:query][arg] = method_args[arg] unless method_args[arg].nil? h[:query][arg] = method_args[arg] unless method_args[arg].nil?
end end
builder.payload_args.each do |arg| if builder.is_json?
h[:body][arg] = method_args[arg] h[:body] = h[:body].to_json
h[:headers] = {:"Content-Type" => "application/json"} if builder.is_json?
end end
end end
......
# frozen_string_literal: true
module Dingtalk module Dingtalk
module RequestUrl module RequestUrl
# 获取 AccessToken # 获取 AccessToken
ACCESS_TOKEN = "https://oapi.dingtalk.com/gettoken".freeze ACCESS_TOKEN = "https://oapi.dingtalk.com/gettoken"
# 扫码登录第三方网站 OAuth 跳转地址
# @url https://ding-doc.dingtalk.com/doc#/serverapi2/kymkv6
CONNECT_QR_REDIRECT = "https://oapi.dingtalk.com/connect/qrconnect"
# 企业内应用免登录获取用户ID # 企业内应用免登录获取用户ID
# https://ding-doc.dingtalk.com/doc#/serverapi2/clotub # @url https://ding-doc.dingtalk.com/doc#/serverapi2/clotub
GET_USER_INFO = "https://oapi.dingtalk.com/user/getuserinfo".freeze INT_LOGIN_FREE_GET_USER_INFO = "https://oapi.dingtalk.com/user/getuserinfo"
# 服务端通过临时授权码获取授权用户的个人信息 # 服务端通过临时授权码获取授权用户的个人信息
# @url https://ding-doc.dingtalk.com/doc#/serverapi2/etaarr # @url https://ding-doc.dingtalk.com/doc#/serverapi2/etaarr
GET_USER_INFO_SNS = "https://oapi.dingtalk.com/sns/getuserinfo_bycode".freeze GET_USER_INFO_SNS = "https://oapi.dingtalk.com/sns/getuserinfo_bycode"
end end
end end
\ No newline at end of file
...@@ -3,26 +3,70 @@ require "dingtalk/auth" ...@@ -3,26 +3,70 @@ require "dingtalk/auth"
RSpec.describe Dingtalk::Auth do RSpec.describe Dingtalk::Auth do
before :each do before :each do
@app_key = "mocked_app_key"
@app_secret = "mocked_app_secret"
@int_user_id = 100 @int_user_id = 100
@access_token = "mocked_access_token" @access_token = "mocked_access_token"
@int_login_code = "mocked_int_login_code" @int_login_code = "mocked_int_login_code"
@timestamp = (Time.now.localtime("+08:00").to_f * 1000).to_i
@signature = Dingtalk.login_free_signature(@app_secret, timestamp: @timestamp)
response_body = {}.tap do |h| # 企业内部免登 响应结果
get_user_info_response_body = {}.tap do |h|
h[:userid] = @int_user_id h[:userid] = @int_user_id
h[:is_sys] = false h[:is_sys] = false
h[:sys_level] = 0 h[:sys_level] = 0
end end
# 钉钉内免登第三方网站 响应结果
get_user_info_by_code_response_body = {}.tap do |h|
h[:errcode] = 0
h[:errmsg] = "ok"
h[:user_info] = {
nick: "张三",
openid: "liSii8KCxxxxx",
unionid: "7Huu46kk"
}
end
stub_request(:get, /oapi.dingtalk.com\/user\/getuserinfo/) stub_request(:get, /oapi.dingtalk.com\/user\/getuserinfo/)
.with(query: { code: @int_login_code, access_token: @access_token }) .with(query: { code: @int_login_code, access_token: @access_token })
.to_return(status: 200, body: response_body.to_json) .to_return(status: 200, body: get_user_info_response_body.to_json)
stub_request(:post, /oapi.dingtalk.com\/sns\/getuserinfo_bycode/)
.with(
query: { accessKey: @app_key, timestamp: @timestamp.to_s, signature: @signature.to_s },
body: { tmp_auth_code: "23152698ea18304da4d0ce1xxxxx" }.to_json,
headers: { :"Content-Type" => "application/json" }
)
.to_return(status: 200, body: get_user_info_by_code_response_body.to_json)
end end
it 'should get internal app user id in login-free scenario' do it 'should get user id in internal app login-free scenario' do
response = Dingtalk::Auth.get_int_login_free_user_id code: @int_login_code, access_token: @access_token response = Dingtalk::Auth.get_int_login_free_user_id(
code: @int_login_code,
access_token: @access_token,
)
expect(response[:userid]).to eq(@int_user_id) expect(response[:userid]).to eq(@int_user_id)
expect(response[:is_sys]).to eq(false) expect(response[:is_sys]).to eq(false)
expect(response[:sys_level]).to eq(0) expect(response[:sys_level]).to eq(0)
end end
# 详情见 https://ding-doc.dingtalk.com/doc#/serverapi2/etaarr
it 'should get user profile through temp auth code in login-free scenario' do
response = Dingtalk::Auth.get_3rd_login_free_user_profile(
accessKey: @app_key,
timestamp: @timestamp,
signature: @signature.to_s,
tmp_auth_code: "23152698ea18304da4d0ce1xxxxx"
)
expect(response[:user_info]).to be_a(Hash)
expect(response[:user_info][:nick]).to eq("张三")
expect(response[:user_info][:openid]).to eq("liSii8KCxxxxx")
expect(response[:user_info][:unionid]).to eq("7Huu46kk")
end
end end
\ No newline at end of file
...@@ -4,12 +4,13 @@ RSpec.describe Dingtalk do ...@@ -4,12 +4,13 @@ RSpec.describe Dingtalk do
end end
# 测试样本见 https://ding-doc.dingtalk.com/doc#/faquestions/hxs5v9 底部 # 测试样本见 https://ding-doc.dingtalk.com/doc#/faquestions/hxs5v9 底部
it "signature correct timestamp with app_secret in personal login-free scenario" do it "sign correct timestamp with app_secret in personal login-free scenario" do
timestamp = 1546084445901 timestamp = 1546084445901
app_secret = "testappSecret" app_secret = "testappSecret"
signature = Dingtalk.login_free_signature(app_secret, timestamp: timestamp) signature = Dingtalk.login_free_signature(app_secret, timestamp: timestamp)
expect(signature).to eq("HCbG3xNE3vzhO%2Bu7qCUL1jS5hsu2n5r2cFhnTrtyDAE%3D") expect(signature).to be_a(Dingtalk::Signature)
expect(signature.url_encoded).to eq("HCbG3xNE3vzhO%2Bu7qCUL1jS5hsu2n5r2cFhnTrtyDAE%3D")
end end
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment