yol_sso 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +44 -0
- data/LICENSE.txt +22 -0
- data/README.md +371 -0
- data/Rakefile +2 -0
- data/lib/yol_sso.rb +32 -0
- data/lib/yol_sso/client.rb +19 -0
- data/lib/yol_sso/connection.rb +3 -0
- data/lib/yol_sso/connections/base.rb +60 -0
- data/lib/yol_sso/connections/message.rb +15 -0
- data/lib/yol_sso/helpers/pkcs7_encoder.rb +28 -0
- data/lib/yol_sso/helpers/prpcrypt.rb +48 -0
- data/lib/yol_sso/models/configuration.rb +11 -0
- data/lib/yol_sso/models/encrypt_message.rb +24 -0
- data/lib/yol_sso/version.rb +3 -0
- data/pkg/.DS_Store +0 -0
- data/yol_sso.gemspec +27 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 061fba38ddccfb3737e87608089ff75e37b7b0288e0b34b150b6011b2dfbbf40
|
4
|
+
data.tar.gz: 36924d8292b0d51261fb5d71800c3a1fc4790f57f65f297e85c0ac4274337ee6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4aa0e69e03cf2a4951b73b5019234ea9f3cf455879582ef4f122c6c87f30e2860b7143f3fa9e3e49ebf6aefaaafafc0f12dd3e69d157d2c0a2a4d26630ff02d7
|
7
|
+
data.tar.gz: 9f5bf41f3f012d059af1c7dc64f19de2a56fc9fcaef5ac4c612478781140514a46f797953ccc90afb27bfd241289f0315cefa154bc4634e92dd3bc01f397f8f9
|
data/.DS_Store
ADDED
Binary file
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
yol_sso (0.0.1)
|
5
|
+
multi_xml
|
6
|
+
nokogiri
|
7
|
+
roxml
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (6.0.3.2)
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
+
i18n (>= 0.7, < 2)
|
15
|
+
minitest (~> 5.1)
|
16
|
+
tzinfo (~> 1.1)
|
17
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
18
|
+
concurrent-ruby (1.1.9)
|
19
|
+
i18n (1.8.10)
|
20
|
+
concurrent-ruby (~> 1.0)
|
21
|
+
minitest (5.14.4)
|
22
|
+
multi_xml (0.6.0)
|
23
|
+
nokogiri (1.11.7-x86_64-darwin)
|
24
|
+
racc (~> 1.4)
|
25
|
+
racc (1.5.2)
|
26
|
+
rake (13.0.1)
|
27
|
+
roxml (4.1.1)
|
28
|
+
activesupport (>= 4.0)
|
29
|
+
nokogiri (>= 1.3.3)
|
30
|
+
thread_safe (0.3.6)
|
31
|
+
tzinfo (1.2.9)
|
32
|
+
thread_safe (~> 0.1)
|
33
|
+
zeitwerk (2.4.0)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
bundler
|
40
|
+
rake
|
41
|
+
yol_sso!
|
42
|
+
|
43
|
+
BUNDLED WITH
|
44
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,371 @@
|
|
1
|
+
# Sso
|
2
|
+
|
3
|
+
This project rocks and uses MIT-LICENSE.
|
4
|
+
|
5
|
+
https://rubygems.org/gems/yol_qy_weixin
|
6
|
+
|
7
|
+
[](http://badge.fury.io/rb/yol_qy_weixin)
|
8
|
+
|
9
|
+
**有问题请及时提issue**
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem "yol_qy_weixin", git: "https://github.com/luojie2019/yol_qy_weixin.git"
|
13
|
+
```
|
14
|
+
|
15
|
+
# 配置
|
16
|
+
|
17
|
+
## 安装依赖Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'yol_qy_weixin'
|
23
|
+
```
|
24
|
+
## 配置 corpid secret
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# 目录
|
28
|
+
|
29
|
+
file: your_project/config/qy_weixin.yml
|
30
|
+
|
31
|
+
.your_project/
|
32
|
+
├── app
|
33
|
+
├── bin
|
34
|
+
├── config
|
35
|
+
│ ├── redis.yml
|
36
|
+
│ ├── database.yml
|
37
|
+
│ ├── qy_weixin.yml
|
38
|
+
```
|
39
|
+
|
40
|
+
```yml
|
41
|
+
defaults: &defaults
|
42
|
+
corpid: '1**'
|
43
|
+
secret: '2**'
|
44
|
+
|
45
|
+
development: &development
|
46
|
+
<<: *defaults
|
47
|
+
|
48
|
+
test:
|
49
|
+
<<: *defaults
|
50
|
+
|
51
|
+
production:
|
52
|
+
corpid: <%= ENV.fetch('QY_WEIXIN_CORPID') { '1**'' } %>
|
53
|
+
secret: <%= ENV.fetch('QY_WEIXIN_SECRET') { '2**'' } %>
|
54
|
+
```
|
55
|
+
|
56
|
+
**说明:考虑access_token需要缓存,redis配置请参考官方文档 https://github.com/redis/redis-rb/blob/master/README.md**
|
57
|
+
|
58
|
+
## 读取配置
|
59
|
+
```ruby
|
60
|
+
# 读取qy_weixin.yml配置
|
61
|
+
qy_weixin_config = YAML::load(ERB.new(File.read("#{Rails.root}/config/qy_weixin.yml")).result)[Rails.env]
|
62
|
+
```
|
63
|
+
如果你是在Rails框架下,也可以使用Settingslogic读取yml配置:
|
64
|
+
```ruby
|
65
|
+
# 引用gem
|
66
|
+
gem 'settingslogic', '2.0.9'
|
67
|
+
|
68
|
+
# your_project/app/settings/weixin_setting.rb
|
69
|
+
class WeixinSetting < Settingslogic
|
70
|
+
source "#{Rails.root}/config/weixin.yml"
|
71
|
+
namespace Rails.env
|
72
|
+
end
|
73
|
+
|
74
|
+
corpid = WeixinSetting.corpid
|
75
|
+
corp_secret = WeixinSetting.secret
|
76
|
+
```
|
77
|
+
|
78
|
+
## 实例对象
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# 目录 file: your_project/config/initializers/qy_weixin.rb
|
82
|
+
|
83
|
+
QyWexinClient = YolQyWeixin::Client.new(
|
84
|
+
corpid: qy_weixin_config["corpid"],
|
85
|
+
secret: qy_weixin_config["secret"],
|
86
|
+
redis: RedisClient
|
87
|
+
)
|
88
|
+
```
|
89
|
+
|
90
|
+
**说明:RedisClient为redis实例,如果没有配置可传nil,建议使用redis,考虑到access_token获取次数限制;**
|
91
|
+
|
92
|
+
# 基本用法
|
93
|
+
|
94
|
+
如果需要获取的 access_token
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
access_token = QyWexinClient.get_access_token
|
98
|
+
# 返回参考企业微信官方文档:https://work.weixin.qq.com/api/doc/90000/90135/91039
|
99
|
+
```
|
100
|
+
|
101
|
+
## 部门
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
待补充,有需要可提issue到git:https://github.com/luojie2019/yol_qy_weixin.git
|
105
|
+
```
|
106
|
+
|
107
|
+
## 成员
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# 获取成员信息
|
111
|
+
access_token = QyWexinClient.get_user_info(open_id)
|
112
|
+
# 返回参考企业微信官方文档:https://open.work.weixin.qq.com/api/doc/90000/90135/90196
|
113
|
+
|
114
|
+
# 获取反问用户信息
|
115
|
+
access_token = QyWexinClient.get_user_id(code)
|
116
|
+
# 返回参考企业微信官方文档:https://open.work.weixin.qq.com/api/doc/90000/90135/91707
|
117
|
+
```
|
118
|
+
|
119
|
+
|
120
|
+
---
|
121
|
+
|
122
|
+
|
123
|
+
后续功能实现还待优化,有需要可提issue到git:https://github.com/luojie2019/yol_qy_weixin.git
|
124
|
+
|
125
|
+
## 标签
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
group_client.tag.create(name)
|
129
|
+
group_client.tag.update(id, name)
|
130
|
+
group_client.tag.delete(id)
|
131
|
+
group_client.tag.get(id)
|
132
|
+
group_client.tag.add_tag_users(id, user_ids)
|
133
|
+
group_client.tag.delete_tag_users(id, user_ids)
|
134
|
+
group_client.tag.list
|
135
|
+
```
|
136
|
+
|
137
|
+
## 自定义菜单
|
138
|
+
|
139
|
+
menu_json的生成方法请参考:
|
140
|
+
https://github.com/lanrion/weixin_rails_middleware/wiki/DIY-menu
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
group_client.menu.create(menu_json, app_id)
|
144
|
+
group_client.menu.delete(app_id)
|
145
|
+
group_client.menu.get(app_id)
|
146
|
+
```
|
147
|
+
|
148
|
+
## Oauth2用法
|
149
|
+
|
150
|
+
先要配置你应用的 可信域名 `2458023e.ngrok.com`
|
151
|
+
state 为开发者自定义参数,可选
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
# 生成授权url
|
155
|
+
group_client.oauth.authorize_url("http://2458023e.ngrok.com", "state")
|
156
|
+
|
157
|
+
# 获取code后,获取用户信息
|
158
|
+
# app_id: 跳转链接时所在的企业应用ID
|
159
|
+
group_client.oauth.get_user_info("code", "app_id")
|
160
|
+
```
|
161
|
+
|
162
|
+
## 发送消息
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# params: (users, parties, tags, agent_id, content, safe=0)
|
166
|
+
# users, parties, tags 如果是多个用户,传数组,如果是全部,则直接传 "@all"
|
167
|
+
group_client.message.send_text("@all", "@all", "@all", app_id, text_message)
|
168
|
+
```
|
169
|
+
**其他发送消息方法请查看 api/message.rb**
|
170
|
+
|
171
|
+
## 上传多媒体文件
|
172
|
+
```ruby
|
173
|
+
# params: media, media_type
|
174
|
+
group_client.media.upload(image_jpg_file, "image")
|
175
|
+
|
176
|
+
# 获取下载链接
|
177
|
+
# 返回一个URL,请开发者自行使用此url下载
|
178
|
+
group_client.media.get_media_by_id(media_id)
|
179
|
+
|
180
|
+
# 上传永久图文素材
|
181
|
+
# articles 为图文列表:
|
182
|
+
{
|
183
|
+
"title": "Title01",
|
184
|
+
"thumb_media_id": "2-G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0",
|
185
|
+
"author": "zs",
|
186
|
+
"content_source_url": "",
|
187
|
+
"content": "Content001",
|
188
|
+
"digest": "airticle01",
|
189
|
+
"show_cover_pic": "0"
|
190
|
+
}
|
191
|
+
group_client.material.add_mpnews(agent_id, articles)
|
192
|
+
|
193
|
+
# 更新图文素材
|
194
|
+
group_client.material.update_mpnews(agent_id, media_id, articles=[])
|
195
|
+
|
196
|
+
# 上传其他类型永久素材
|
197
|
+
# type: "image", "voice", "video", "file"
|
198
|
+
# file: File
|
199
|
+
group_client.material.add_material(agent_id, type, file)
|
200
|
+
|
201
|
+
# 删除永久素材
|
202
|
+
group_client.material.del(agent_id, media_id)
|
203
|
+
|
204
|
+
# 获取素材总数
|
205
|
+
group_client.material.get_count(agent_id)
|
206
|
+
|
207
|
+
# 获取素材列表
|
208
|
+
group_client.material.list(agent_id, type, offset, count=20)
|
209
|
+
```
|
210
|
+
|
211
|
+
## 第三方应用
|
212
|
+
|
213
|
+
这里特别注意:保留 suite_access_token的cache是直接利用了前文配置的cache_store缓存。
|
214
|
+
|
215
|
+
### api 使用介绍
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
suite_api = QyWechatApi::Suite.service(suite_id, suite_secret, suite_ticket)
|
219
|
+
|
220
|
+
# 获取预授权码
|
221
|
+
suite_api.get_pre_auth_code(appid=[])
|
222
|
+
|
223
|
+
# 获取企业号的永久授权码
|
224
|
+
suite_api.get_permanent_code(auth_code)
|
225
|
+
|
226
|
+
# 获取企业号的授权信息
|
227
|
+
suite_api.get_auth_info(auth_corpid, code)
|
228
|
+
|
229
|
+
# 获取企业号应用
|
230
|
+
suite_api.get_agent(auth_corpid, code, agent_id)
|
231
|
+
|
232
|
+
# 设置授权方的企业应用的选项设置信息
|
233
|
+
suite_api.set_agent(auth_corpid, permanent_code, agent_info)
|
234
|
+
|
235
|
+
# 调用企业接口所需的access_token
|
236
|
+
suite_api.get_corp_token(auth_corpid, permanent_code)
|
237
|
+
|
238
|
+
# 生成授权URL
|
239
|
+
suite_api.auth_url(code, uri, state="suite")
|
240
|
+
|
241
|
+
```
|
242
|
+
|
243
|
+
## 企业号登录授权
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
# 获取登录授权URL
|
247
|
+
# state default 'qy_wechat', option
|
248
|
+
# 此处授权回调时会传递auth_code、expires_in,auth_code用于get_login_info(获取企业号管理员登录信息)接口使用
|
249
|
+
group_client.auth_login.auth_login_url("redirect_uri", "state")
|
250
|
+
|
251
|
+
# 获取应用提供商凭证
|
252
|
+
# provider_secret:提供商的secret,在提供商管理页面可见
|
253
|
+
# 此处会返回:provider_access_token(已通过QyWechatApi.cache缓存7100s)
|
254
|
+
group_client.auth_login.get_provider_token(provider_secret)
|
255
|
+
|
256
|
+
# 通过传递provider_access_token,获取企业号管理员登录信息
|
257
|
+
group_client.auth_login.get_login_info(auth_code, provider_access_token)
|
258
|
+
|
259
|
+
# 通过传递provider_secret,获取企业号管理员登录信息
|
260
|
+
group_client.auth_login.get_login_info_by_secret(auth_code, provider_secret)
|
261
|
+
|
262
|
+
# 获取登录企业号官网的url
|
263
|
+
group_client.auth_login.get_login_url(ticket, provider_token, target, agentid=nil)
|
264
|
+
```
|
265
|
+
|
266
|
+
## 异步任务接口
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# 邀请成员关注
|
270
|
+
group_client.async_task.invite_user(callback, invite_info={})
|
271
|
+
# 增量更新成员
|
272
|
+
group_client.async_task.sync_user(callback, media_id)
|
273
|
+
# 全量覆盖成员
|
274
|
+
group_client.async_task.replace_user(callback, media_id)
|
275
|
+
# 全量覆盖部门
|
276
|
+
group_client.async_task.replace_party(callback, media_id)
|
277
|
+
# 获取异步任务结果
|
278
|
+
group_client.async_task.get_result(job_id)
|
279
|
+
```
|
280
|
+
|
281
|
+
## 获取js api签名包
|
282
|
+
```ruby
|
283
|
+
group_client.sign_package(request.url)
|
284
|
+
```
|
285
|
+
|
286
|
+
## 管理企业号应用
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
# 获取应用概况列表
|
290
|
+
group_client.agent.list
|
291
|
+
|
292
|
+
# 设置企业号应用
|
293
|
+
# agentid 企业应用的id
|
294
|
+
# report_location_flag 企业应用是否打开地理位置上报 0:不上报;1:进入会话上报;2:持续上报
|
295
|
+
# logo_mediaid 企业应用头像的mediaid,通过多媒体接口上传图片获得mediaid,上传后会自动裁剪成方形和圆形两个头像
|
296
|
+
# name 企业应用名称
|
297
|
+
# description 企业应用详情
|
298
|
+
# redirect_domain 企业应用可信域名
|
299
|
+
# isreportuser 是否接收用户变更通知。0:不接收;1:接收
|
300
|
+
# isreportenter 是否上报用户进入应用事件。0:不接收;1:接收
|
301
|
+
group_client.agent.set()
|
302
|
+
|
303
|
+
## 获取企业号应用
|
304
|
+
group_client.agent.get(agent_id)
|
305
|
+
```
|
306
|
+
|
307
|
+
### 应用套件的回调通知处理
|
308
|
+
|
309
|
+
Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AC%AC%E4%B8%89%E6%96%B9%E5%9B%9E%E8%B0%83%E5%8D%8F%E8%AE%AE
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
class QyServicesController < ApplicationController
|
313
|
+
skip_before_filter :verify_authenticity_token, only: :receive_ticket
|
314
|
+
|
315
|
+
# TODO: 需要创建表: suites
|
316
|
+
def receive_ticket
|
317
|
+
param_xml = request.body.read
|
318
|
+
aes_key = "NJgquXf6vnYlGpD5APBqlndAq7Nx8fToiEz5Wbaka47"
|
319
|
+
aes_key = Base64.decode64("#{aes_key}=")
|
320
|
+
hash = MultiXml.parse(param_xml)['xml']
|
321
|
+
@body_xml = OpenStruct.new(hash)
|
322
|
+
suite_id = "tj86cd0f5b8f7ce20d"
|
323
|
+
content = QyWechat::Prpcrypt.decrypt(aes_key, @body_xml.Encrypt, suite_id)[0]
|
324
|
+
hash = MultiXml.parse(content)["xml"]
|
325
|
+
Rails.logger.info hash
|
326
|
+
render text: "success"
|
327
|
+
# {"SuiteId"=>"tj86cd0f5b8f7ce20d",
|
328
|
+
# "SuiteTicket"=>"Pb5M0PEQFZSNondlK1K_atu2EoobY9piMcQCdE3URiCG3aTwX5WBTQaSsqCzaD-0",
|
329
|
+
# "InfoType"=>"suite_ticket",
|
330
|
+
# "TimeStamp"=>"1426988061"}
|
331
|
+
end
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
### 企业号消息接口
|
336
|
+
|
337
|
+
Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BC%81%E4%B8%9A%E5%8F%B7%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
group_client.chat.send_single_text(sender, user_id, msg)
|
341
|
+
group_client.chat.send_single_image(sender, user_id, media_id)
|
342
|
+
group_client.chat.send_single_file(sender, user_id, media_id)
|
343
|
+
group_client.chat.send_group_text(sender, chat_id, msg)
|
344
|
+
group_client.chat.send_group_image(sender, chat_id, media_id)
|
345
|
+
group_client.chat.send_group_file(sender, chat_id, media_id)
|
346
|
+
```
|
347
|
+
|
348
|
+
### 企业客服服务
|
349
|
+
|
350
|
+
Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=企业客服接口说明
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
# msg_struct请根据文档结构拼接传入
|
354
|
+
group_client.kf.send(msg_struct)
|
355
|
+
```
|
356
|
+
|
357
|
+
### 企业号摇一摇周边
|
358
|
+
|
359
|
+
Wiki: http://qydev.weixin.qq.com/wiki/index.php?title=获取设备及用户信息
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
# 获取设备及用户信息
|
363
|
+
# 摇周边业务的ticket,可在摇到的URL中得到,ticket生效时间为30分钟,每一次摇都会重新生成新的ticket
|
364
|
+
group_client.get_shake_info(ticket)
|
365
|
+
```
|
366
|
+
|
367
|
+
## 捐赠支持
|
368
|
+
|
369
|
+
如果你觉得我的gem对你有帮助,欢迎打赏支持,:smile:
|
370
|
+
|
371
|
+

|
data/Rakefile
ADDED
data/lib/yol_sso.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'roxml'
|
2
|
+
require 'multi_xml'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'net/http'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require "yol_sso/version"
|
8
|
+
|
9
|
+
require "yol_sso/models/configuration"
|
10
|
+
require "yol_sso/models/encrypt_message"
|
11
|
+
require "yol_sso/helpers/prpcrypt"
|
12
|
+
|
13
|
+
require "yol_sso/connection"
|
14
|
+
require "yol_sso/client"
|
15
|
+
|
16
|
+
module YolSso
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
# A Sso configuration object. See Sso::Configuration.
|
21
|
+
attr_writer :configuration
|
22
|
+
|
23
|
+
def configure
|
24
|
+
yield(configuration)
|
25
|
+
end
|
26
|
+
|
27
|
+
def configuration
|
28
|
+
@configuration ||= Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "monitor"
|
2
|
+
require "redis"
|
3
|
+
require 'digest/md5'
|
4
|
+
module YolSso
|
5
|
+
class Client
|
6
|
+
|
7
|
+
include Connection::Base
|
8
|
+
include Connection::Message
|
9
|
+
|
10
|
+
attr_accessor :host
|
11
|
+
|
12
|
+
attr_accessor :agentid
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
@host = options[:host] || Sso.configuration.host
|
16
|
+
@agentid = options[:agentid] || Sso.configuration.agentid
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module YolSso
|
2
|
+
module Connection
|
3
|
+
module Base
|
4
|
+
def http_post(url, params)
|
5
|
+
uri = URI(url)
|
6
|
+
req = Net::HTTP.new(uri.host, uri.port)
|
7
|
+
header = {'content-type': 'application/json'}
|
8
|
+
req.use_ssl = true if uri.scheme == 'https'
|
9
|
+
res = req.post("#{uri.path}?#{uri.query}", params.to_json, header)
|
10
|
+
handle_res(res)
|
11
|
+
end
|
12
|
+
|
13
|
+
def http_get(url)
|
14
|
+
uri = URI(url)
|
15
|
+
req = Net::HTTP.new(uri.host, uri.port)
|
16
|
+
req.use_ssl = true if uri.scheme == 'https'
|
17
|
+
res = req.get("#{uri.path}?#{uri.query}")
|
18
|
+
handle_res(res)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_access_token
|
22
|
+
if redis.nil?
|
23
|
+
access_token_res = get_token(corpid, secret)
|
24
|
+
access_token = access_token_res["access_token"] rescue nil
|
25
|
+
else
|
26
|
+
access_token = redis.get("qywx_access_token")
|
27
|
+
if access_token.nil?
|
28
|
+
access_token_res = get_token(corpid, secret)
|
29
|
+
access_token = access_token_res["access_token"] rescue nil
|
30
|
+
if access_token.nil?
|
31
|
+
raise Exception.new("QyWeixin access token authorize false, corpid: #{corpid}")
|
32
|
+
else
|
33
|
+
redis.set("qywx_access_token", access_token)
|
34
|
+
redis.expire("qywx_access_token", 7200)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
access_token
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def get_token(app_id, app_secret)
|
44
|
+
http_get(token_url(app_id, app_secret))
|
45
|
+
end
|
46
|
+
|
47
|
+
def token_url(corpid, secret)
|
48
|
+
"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=#{corpid}&corpsecret=#{secret}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_res(res)
|
52
|
+
if res.code == '200'
|
53
|
+
return JSON.parse(res.body)
|
54
|
+
else
|
55
|
+
return {:code => res.code}.to_json
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module YolSso
|
4
|
+
module PKCS7Encoder
|
5
|
+
extend self
|
6
|
+
|
7
|
+
BLOCK_SIZE = 32
|
8
|
+
|
9
|
+
def decode(text)
|
10
|
+
pad = text[-1].ord
|
11
|
+
pad = 0 if (pad < 1 || pad > BLOCK_SIZE)
|
12
|
+
size = text.size - pad
|
13
|
+
text[0...size]
|
14
|
+
end
|
15
|
+
|
16
|
+
# 对需要加密的明文进行填充补位
|
17
|
+
# 返回补齐明文字符串
|
18
|
+
def encode(text)
|
19
|
+
# 计算需要填充的位数
|
20
|
+
amount_to_pad = BLOCK_SIZE - (text.length % BLOCK_SIZE)
|
21
|
+
amount_to_pad = BLOCK_SIZE if amount_to_pad == 0
|
22
|
+
# 获得补位所用的字符
|
23
|
+
pad_chr = amount_to_pad.chr
|
24
|
+
"#{text}#{pad_chr * amount_to_pad}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "yol_sso/helpers/pkcs7_encoder"
|
3
|
+
module YolSso
|
4
|
+
module Prpcrypt
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# 对密文进行解密.
|
8
|
+
# text 需要解密的密文
|
9
|
+
def decrypt(aes_key, text, app_id)
|
10
|
+
status = 200
|
11
|
+
text = Base64.decode64(text)
|
12
|
+
text = handle_cipher(:decrypt, aes_key, text)
|
13
|
+
result = PKCS7Encoder.decode(text)
|
14
|
+
content = result[16...result.length]
|
15
|
+
len_list = content[0...4].unpack("N")
|
16
|
+
xml_len = len_list[0]
|
17
|
+
xml_content = content[4...4 + xml_len]
|
18
|
+
from_app_id = content[xml_len + 4...content.size]
|
19
|
+
# TODO: refactor
|
20
|
+
if app_id != from_app_id
|
21
|
+
# raise BizErr, "#{__FILE__}:#{__LINE__} Failure because app_id != from_app_id"
|
22
|
+
status = 500
|
23
|
+
end
|
24
|
+
[xml_content, status]
|
25
|
+
end
|
26
|
+
|
27
|
+
# 加密
|
28
|
+
def encrypt(aes_key, text, app_id)
|
29
|
+
text = text.force_encoding("ASCII-8BIT")
|
30
|
+
random = SecureRandom.hex(8)
|
31
|
+
msg_len = [text.length].pack("N")
|
32
|
+
text = "#{random}#{msg_len}#{text}#{app_id}"
|
33
|
+
text = PKCS7Encoder.encode(text)
|
34
|
+
text = handle_cipher(:encrypt, aes_key, text)
|
35
|
+
Base64.encode64(text)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def handle_cipher(action, aes_key, text)
|
40
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
41
|
+
cipher.send(action)
|
42
|
+
cipher.padding = 0
|
43
|
+
cipher.key = aes_key
|
44
|
+
cipher.iv = aes_key[0...16]
|
45
|
+
cipher.update(text) + cipher.final
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# 标准的回包
|
3
|
+
# <xml>
|
4
|
+
# <Encrypt><![CDATA[msg_encrypt]]></Encrypt>
|
5
|
+
# <MsgSignature><![CDATA[msg_signature]]></MsgSignature>
|
6
|
+
# <TimeStamp>timestamp</TimeStamp>
|
7
|
+
# <Nonce><![CDATA[nonce]]></Nonce>
|
8
|
+
# </xml>
|
9
|
+
|
10
|
+
module YolSso
|
11
|
+
class EncryptMessage
|
12
|
+
include ROXML
|
13
|
+
xml_name :xml
|
14
|
+
|
15
|
+
xml_accessor :Encrypt, :cdata => true
|
16
|
+
xml_accessor :Nonce, :cdata => true
|
17
|
+
xml_accessor :TimeStamp, :as => Integer
|
18
|
+
xml_accessor :MsgSignature, :cdata => true
|
19
|
+
|
20
|
+
def to_xml
|
21
|
+
super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/pkg/.DS_Store
ADDED
Binary file
|
data/yol_sso.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'yol_sso/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "yol_sso"
|
8
|
+
spec.version = YolSso::VERSION
|
9
|
+
spec.authors = ["luojie"]
|
10
|
+
spec.email = ["luojie@yolanda.hk"]
|
11
|
+
spec.summary = %q{Yolanda middleware for Message.}
|
12
|
+
spec.description = ""
|
13
|
+
spec.homepage = "https://github.com/luojie2019/yol_sso"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_dependency 'nokogiri'
|
25
|
+
spec.add_dependency 'multi_xml'
|
26
|
+
spec.add_dependency 'roxml'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yol_sso
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- luojie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: multi_xml
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: roxml
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: ''
|
84
|
+
email:
|
85
|
+
- luojie@yolanda.hk
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".DS_Store"
|
91
|
+
- Gemfile
|
92
|
+
- Gemfile.lock
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/yol_sso.rb
|
97
|
+
- lib/yol_sso/client.rb
|
98
|
+
- lib/yol_sso/connection.rb
|
99
|
+
- lib/yol_sso/connections/base.rb
|
100
|
+
- lib/yol_sso/connections/message.rb
|
101
|
+
- lib/yol_sso/helpers/pkcs7_encoder.rb
|
102
|
+
- lib/yol_sso/helpers/prpcrypt.rb
|
103
|
+
- lib/yol_sso/models/configuration.rb
|
104
|
+
- lib/yol_sso/models/encrypt_message.rb
|
105
|
+
- lib/yol_sso/version.rb
|
106
|
+
- pkg/.DS_Store
|
107
|
+
- yol_sso.gemspec
|
108
|
+
homepage: https://github.com/luojie2019/yol_sso
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.7.9
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Yolanda middleware for Message.
|
132
|
+
test_files: []
|