wechat 0.7.8 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53ffffaf24d8d96f29551c79c5de7ffa1829a918
4
- data.tar.gz: d98f374eda600f2c61a1ce1dfcbe408663538b08
3
+ metadata.gz: 5457309cd442e525f5769f1fb632c3d9e5c0eb10
4
+ data.tar.gz: a8fbea864c6c3e26ed689628ec137818864b6479
5
5
  SHA512:
6
- metadata.gz: f3a9d2da9c57c99b535c5f1f29a36f796100f23997386cfe922415df30aeb5d32e0e0634e99f6e5cf58e3ba2d1bf8b0e6a47bccdc4d3e1eb2db84d48fff9af61
7
- data.tar.gz: f8913573a84ca830f21b14be23d446b336ef4daa010ec2501185dd3da102cc98244ef3e6ddc74d72b7b9781ce452e7065f8614b86690c62148742a564c771563
6
+ metadata.gz: bee8b03680fa64611e07fd7ce78f5c0683b4fb1877e79240aabd5ab22c330a9e1d9ad0f2a56444bbf53e8722d1b1026203b9d5d43565a99d0ebb0186f9bcea68
7
+ data.tar.gz: f368347ad7a5601cdf7ab77390e3bab3a46a6a07bd9673639762d986315ce6befa6aac438a1d11c4f9922912b96c8280957513003d49ff4b68efc18ae7e092b6
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.7.9 (released at 4/12/2016)
4
+
5
+ * wechat_oauth2 support public account now.
6
+ * Refresh and store state on jsapi ticket, using it on oauth2_url to more secure.
7
+ * Remove extra sending payload in message template send json
8
+ * Allow setting oauth2_cookie_duration in config
9
+
3
10
  ## v0.7.8 (released at 3/31/2016)
4
11
 
5
12
  * New wechat_api, similar to wechat_responder, but without messange handle DSL, support web page only wechat application
@@ -98,12 +98,15 @@ default: &default
98
98
  secret: "app_secret"
99
99
  token: "app_token"
100
100
  access_token: "/var/tmp/wechat_access_token"
101
+ jsapi_ticket: "/var/tmp/wechat_jsapi_ticket"
101
102
 
102
103
  production:
103
104
  appid: <%= ENV['WECHAT_APPID'] %>
104
105
  secret: <%= ENV['WECHAT_APP_SECRET'] %>
105
106
  token: <%= ENV['WECHAT_TOKEN'] %>
106
- access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
107
+ access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
108
+ jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
109
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
107
110
 
108
111
  development:
109
112
  <<: *default
@@ -143,6 +146,7 @@ production:
143
146
  skip_verify_ssl: true
144
147
  encoding_aes_key: <%= ENV['WECHAT_ENCODING_AES_KEY'] %>
145
148
  jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
149
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
146
150
 
147
151
  development:
148
152
  <<: *default
@@ -196,7 +200,21 @@ end
196
200
 
197
201
  #### OAuth2.0验证接口支持
198
202
 
199
- 目前企业号可以使用如下代码直接取得用户企业号userid:
203
+ 公众号可使用如下代码取得关注用户的相关信息。
204
+
205
+ ```ruby
206
+ class CartController < ActionController::Base
207
+ wechat_api
208
+ def index
209
+ wechat_oauth2 do |openid|
210
+ @current_user = User.find_by(wechat_openid: openid)
211
+ @articles = @current_user.articles
212
+ end
213
+ end
214
+ end
215
+ ```
216
+
217
+ 企业号可使用如下代码取得企业用户的相关信息。
200
218
 
201
219
  ```ruby
202
220
  class WechatsController < ActionController::Base
@@ -212,7 +230,7 @@ class WechatsController < ActionController::Base
212
230
  end
213
231
  ```
214
232
 
215
- `wechat_oauth2`封装了OAuth2.0验证接口和cookie处理逻辑,用户仅需提供业务代码块即可,userid就是微信成员UserID。
233
+ `wechat_oauth2`封装了OAuth2.0验证接口和cookie处理逻辑,用户仅需提供业务代码块即可。userid指的是微信企业成员UserID,openid是关注该公众号的用户openid
216
234
 
217
235
  ## 关于接口权限
218
236
 
data/README.md CHANGED
@@ -108,12 +108,15 @@ default: &default
108
108
  secret: "app_secret"
109
109
  token: "app_token"
110
110
  access_token: "/var/tmp/wechat_access_token"
111
+ jsapi_ticket: "/var/tmp/wechat_jsapi_ticket"
111
112
 
112
113
  production:
113
114
  appid: <%= ENV['WECHAT_APPID'] %>
114
115
  secret: <%= ENV['WECHAT_APP_SECRET'] %>
115
116
  token: <%= ENV['WECHAT_TOKEN'] %>
116
- access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
117
+ access_token: <%= ENV['WECHAT_ACCESS_TOKEN'] %>
118
+ jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
119
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
117
120
 
118
121
  development:
119
122
  <<: *default
@@ -156,6 +159,7 @@ production:
156
159
  skip_verify_ssl: true # not recommend
157
160
  encoding_aes_key: <%= ENV['WECHAT_ENCODING_AES_KEY'] %>
158
161
  jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
162
+ oauth2_cookie_duration: <%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
159
163
 
160
164
  development:
161
165
  <<: *default
@@ -209,7 +213,21 @@ Configure the `trusted_domain_fullname` if you are in development mode and app r
209
213
 
210
214
  #### OAuth2.0 authentication
211
215
 
212
- For enterprise account, user can using userid directly by provide a block in wechat_oauth2:
216
+ For public account, below code will get flollowing user's info.
217
+
218
+ ```ruby
219
+ class CartController < ActionController::Base
220
+ wechat_api
221
+ def index
222
+ wechat_oauth2 do |openid|
223
+ @current_user = User.find_by(wechat_openid: openid)
224
+ @articles = @current_user.articles
225
+ end
226
+ end
227
+ end
228
+ ```
229
+
230
+ For enterprise account, below code will get enterprise member's userinfo.
213
231
 
214
232
  ```ruby
215
233
  class WechatsController < ActionController::Base
@@ -225,7 +243,7 @@ class WechatsController < ActionController::Base
225
243
  end
226
244
  ```
227
245
 
228
- `wechat_oauth2` already implement the necessory OAuth2.0 and cookie logic, userid available as a member UserID for the whole block.
246
+ `wechat_oauth2` already implement the necessory OAuth2.0 and cookie logic. userid defined as the enterprise member UserID. openid defined as the user who following the public account, also notice openid will be different for the same user for different following public account.
229
247
 
230
248
 
231
249
  ## The API privilege
@@ -22,6 +22,7 @@ module ActionController
22
22
  self.skip_verify_ssl = opts[:skip_verify_ssl]
23
23
  self.encoding_aes_key = opts[:encoding_aes_key] || Wechat.config.encoding_aes_key
24
24
  self.trusted_domain_fullname = opts[:trusted_domain_fullname] || Wechat.config.trusted_domain_fullname
25
+ self.oauth2_cookie_duration = opts[:oauth2_cookie_duration] || Wechat.config.oauth2_cookie_duration || 1.hour
25
26
 
26
27
  return self.wechat = Wechat.api if opts.empty?
27
28
  if corpid.present?
@@ -24,7 +24,8 @@ production:
24
24
  access_token: <%%= ENV['WECHAT_ACCESS_TOKEN'] %>
25
25
  encrypt_mode: false # if true must fill encoding_aes_key
26
26
  encoding_aes_key: <%%= ENV['WECHAT_ENCODING_AES_KEY'] %>
27
- jsapi_ticket: <%= ENV['WECHAT_JSAPI_TICKET'] %>
27
+ jsapi_ticket: <%%= ENV['WECHAT_JSAPI_TICKET'] %>
28
+ oauth2_cookie_duration: <%%= ENV['WECHAT_OAUTH2_COOKIE_DURATION'] %>
28
29
 
29
30
  development:
30
31
  <<: *default
@@ -1,15 +1,14 @@
1
1
  require 'wechat/api_base'
2
- require 'wechat/client'
2
+ require 'wechat/http_client'
3
3
  require 'wechat/token/public_access_token'
4
4
  require 'wechat/ticket/public_jsapi_ticket'
5
5
 
6
6
  module Wechat
7
7
  class Api < ApiBase
8
8
  API_BASE = 'https://api.weixin.qq.com/cgi-bin/'.freeze
9
- OAUTH2_BASE = 'https://api.weixin.qq.com/sns/oauth2/'.freeze
10
9
 
11
10
  def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
12
- @client = Client.new(API_BASE, timeout, skip_verify_ssl)
11
+ @client = HttpClient.new(API_BASE, timeout, skip_verify_ssl)
13
12
  @access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
14
13
  @jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
15
14
  end
@@ -130,8 +129,8 @@ module Wechat
130
129
  get 'customservice/getonlinekflist'
131
130
  end
132
131
 
133
- # http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
134
- # 第二步:通过code换取网页授权access_token
132
+ OAUTH2_BASE = 'https://api.weixin.qq.com/sns/'.freeze
133
+
135
134
  def web_access_token(code)
136
135
  params = {
137
136
  appid: access_token.appid,
@@ -139,7 +138,24 @@ module Wechat
139
138
  code: code,
140
139
  grant_type: 'authorization_code'
141
140
  }
142
- get 'access_token', params: params, base: OAUTH2_BASE
141
+ client.get 'oauth2/access_token', params: params, base: OAUTH2_BASE
142
+ end
143
+
144
+ def web_auth_access_token(web_access_token, openid)
145
+ client.get 'auth', params: { access_token: web_access_token, openid: openid }, base: OAUTH2_BASE
146
+ end
147
+
148
+ def web_refresh_access_token(user_refresh_token)
149
+ params = {
150
+ appid: access_token.appid,
151
+ grant_type: 'refresh_token',
152
+ refresh_token: user_refresh_token
153
+ }
154
+ client.get 'oauth2/refresh_token', params: params, base: OAUTH2_BASE
155
+ end
156
+
157
+ def web_userinfo(web_access_token, openid, lang = 'zh_CN')
158
+ client.get 'sns/userinfo', params: { access_token: web_access_token, openid: openid, lang: lang }, base: OAUTH2_BASE
143
159
  end
144
160
  end
145
161
  end
@@ -1,5 +1,4 @@
1
1
  require 'openssl/cipher'
2
- require 'securerandom'
3
2
  require 'base64'
4
3
 
5
4
  module Wechat
@@ -3,14 +3,15 @@ module Wechat
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- attr_accessor :wechat, :token, :appid, :corpid, :agentid, :encrypt_mode, :timeout, :skip_verify_ssl, :encoding_aes_key, :trusted_domain_fullname
6
+ attr_accessor :wechat, :token, :appid, :corpid, :agentid, :encrypt_mode, :timeout,
7
+ :skip_verify_ssl, :encoding_aes_key, :trusted_domain_fullname, :oauth2_cookie_duration
7
8
  end
8
9
 
9
10
  def wechat
10
11
  self.class.wechat # Make sure user can continue access wechat at instance level similar to class level
11
12
  end
12
13
 
13
- def wechat_oauth2(scope = 'snsapi_base', page_url = nil)
14
+ def wechat_oauth2(scope = 'snsapi_base', page_url = nil, &block)
14
15
  appid = self.class.corpid || self.class.appid
15
16
  page_url ||= if self.class.trusted_domain_fullname
16
17
  "#{self.class.trusted_domain_fullname}#{request.original_fullpath}"
@@ -18,17 +19,38 @@ module Wechat
18
19
  request.original_url
19
20
  end
20
21
  redirect_uri = CGI.escape(page_url)
21
- oauth2_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{appid}&redirect_uri=#{redirect_uri}&response_type=code&scope=#{scope}#wechat_redirect"
22
+ oauth2_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{appid}&redirect_uri=#{redirect_uri}&response_type=code&scope=#{scope}&state=#{wechat.jsapi_ticket.oauth2_state}#wechat_redirect"
22
23
 
23
24
  return oauth2_url unless block_given?
24
- raise 'Currently wechat_oauth2 only support enterprise account.' unless self.class.corpid
25
+ if self.class.corpid
26
+ wechat_corp_oauth2(oauth2_url, &block)
27
+ else
28
+ wechat_public_oauth2(oauth2_url, &block)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def wechat_public_oauth2(oauth2_url)
35
+ if cookies.signed_or_encrypted[:we_openid].blank? && params[:code].blank?
36
+ redirect_to oauth2_url
37
+ elsif cookies.signed_or_encrypted[:we_openid].blank? && params[:code].present? && params[:state] == wechat.jsapi_ticket.oauth2_state
38
+ access_info = wechat.web_access_token(params[:code])
39
+ cookies.signed_or_encrypted[:we_openid] = { value: access_info['openid'], expires: self.class.oauth2_cookie_duration.from_now }
40
+ yield access_info['openid'], access_info
41
+ else
42
+ yield cookies.signed_or_encrypted[:we_openid], { 'openid' => cookies.signed_or_encrypted[:we_openid] }
43
+ end
44
+ end
45
+
46
+ def wechat_corp_oauth2(oauth2_url)
25
47
  if cookies.signed_or_encrypted[:we_deviceid].blank? && params[:code].blank?
26
48
  redirect_to oauth2_url
27
- elsif cookies.signed_or_encrypted[:we_deviceid].blank? && params[:code].present?
49
+ elsif cookies.signed_or_encrypted[:we_deviceid].blank? && params[:code].present? && params[:state] == wechat.jsapi_ticket.oauth2_state
28
50
  userinfo = wechat.getuserinfo(params[:code])
29
- cookies.signed_or_encrypted[:we_userid] = { value: userinfo['UserId'], expires: 1.hour.from_now }
30
- cookies.signed_or_encrypted[:we_deviceid] = { value: userinfo['DeviceId'], expires: 1.hour.from_now }
31
- cookies.signed_or_encrypted[:we_openid] = { value: userinfo['OpenId'], expires: 1.hour.from_now }
51
+ cookies.signed_or_encrypted[:we_userid] = { value: userinfo['UserId'], expires: self.class.oauth2_cookie_duration.from_now }
52
+ cookies.signed_or_encrypted[:we_deviceid] = { value: userinfo['DeviceId'], expires: self.class.oauth2_cookie_duration.from_now }
53
+ cookies.signed_or_encrypted[:we_openid] = { value: userinfo['OpenId'], expires: self.class.oauth2_cookie_duration.from_now }
32
54
  yield userinfo['UserId'], userinfo
33
55
  else
34
56
  yield cookies.signed_or_encrypted[:we_userid], { 'UserId' => cookies.signed_or_encrypted[:we_userid],
@@ -1,5 +1,5 @@
1
1
  require 'wechat/api_base'
2
- require 'wechat/client'
2
+ require 'wechat/http_client'
3
3
  require 'wechat/token/corp_access_token'
4
4
  require 'wechat/ticket/corp_jsapi_ticket'
5
5
  require 'cgi'
@@ -11,7 +11,7 @@ module Wechat
11
11
  API_BASE = 'https://qyapi.weixin.qq.com/cgi-bin/'.freeze
12
12
 
13
13
  def initialize(appid, secret, token_file, agentid, timeout, skip_verify_ssl, jsapi_ticket_file)
14
- @client = Client.new(API_BASE, timeout, skip_verify_ssl)
14
+ @client = HttpClient.new(API_BASE, timeout, skip_verify_ssl)
15
15
  @access_token = Token::CorpAccessToken.new(@client, appid, secret, token_file)
16
16
  @agentid = agentid
17
17
  @jsapi_ticket = Ticket::CorpJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
@@ -1,7 +1,7 @@
1
1
  require 'http'
2
2
 
3
3
  module Wechat
4
- class Client
4
+ class HttpClient
5
5
  attr_reader :base, :ssl_context
6
6
 
7
7
  def initialize(base, timeout, skip_verify_ssl)
@@ -39,10 +39,10 @@ module Wechat
39
39
  private
40
40
 
41
41
  def request(path, header = {}, &_block)
42
- url = "#{header.delete(:base) || base}#{path}"
42
+ url_base = header.delete(:base) || base
43
43
  as = header.delete(:as)
44
44
  header['Accept'] = 'application/json'
45
- response = yield(url, header)
45
+ response = yield("#{url_base}#{path}", header)
46
46
 
47
47
  raise "Request not OK, response status #{response.status}" if response.status != 200
48
48
  parse_response(response, as || :json) do |parse_as, data|
@@ -115,7 +115,7 @@ module Wechat
115
115
  update(MsgType: 'music', Music: music_fields)
116
116
  end
117
117
 
118
- def news(collection, &block)
118
+ def news(collection, &_block)
119
119
  if block_given?
120
120
  article = ArticleBuilder.new
121
121
  collection.take(10).each_with_index { |item, index| yield(article, item, index) }
@@ -164,7 +164,7 @@ module Wechat
164
164
  when 'news'
165
165
  json_hash['news'] = { 'articles' => json_hash.delete('articles') }
166
166
  when 'template'
167
- json_hash.merge! json_hash['template']
167
+ json_hash = { 'touser' => json_hash['touser'] }.merge!(json_hash['template'])
168
168
  end
169
169
  JSON.generate(json_hash)
170
170
  end
@@ -106,7 +106,7 @@ module Wechat
106
106
  yield(* user_defined_view_responders(message[:EventKey]), message[:EventKey])
107
107
  elsif 'click' == message[:Event]
108
108
  yield(* match_responders(responders, message[:EventKey]))
109
- elsif known_scan_key_lists.include?(message[:EventKey]) && %w(scan subscribe scancode_push scancode_waitmsg).include?(message[:Event])
109
+ elsif known_scan_key_lists.include?(message[:EventKey]) && %w(scan subscribe scancode_push scancode_waitmsg).freeze.include?(message[:Event])
110
110
  yield(* known_scan_with_match_responders(user_defined_scan_responders, message))
111
111
  elsif 'batch_job_result' == message[:Event]
112
112
  yield(* user_defined_batch_job_responders(message[:BatchJob][:JobType]), message[:BatchJob])
@@ -142,9 +142,9 @@ module Wechat
142
142
 
143
143
  def known_scan_with_match_responders(responders, message)
144
144
  matched = responders.each_with_object({}) do |responder, memo|
145
- if %w(scan subscribe).include?(message[:Event]) && message[:EventKey] == responder[:with]
145
+ if %w(scan subscribe).freeze.include?(message[:Event]) && message[:EventKey] == responder[:with]
146
146
  memo[:scaned] ||= [responder, message[:Ticket]]
147
- elsif %w(scancode_push scancode_waitmsg).include?(message[:Event]) && message[:EventKey] == responder[:with]
147
+ elsif %w(scancode_push scancode_waitmsg).freeze.include?(message[:Event]) && message[:EventKey] == responder[:with]
148
148
  memo[:scaned] ||= [responder, message[:ScanCodeInfo][:ScanResult], message[:ScanCodeInfo][:ScanType]]
149
149
  end
150
150
  end
@@ -5,6 +5,7 @@ module Wechat
5
5
  class CorpJsapiTicket < JsapiBase
6
6
  def refresh
7
7
  data = client.get('get_jsapi_ticket', params: { access_token: access_token.token })
8
+ @oauth2_state = data['oauth2_state'] = SecureRandom.hex(16)
8
9
  write_ticket_to_store(data)
9
10
  read_ticket_from_store
10
11
  end
@@ -1,9 +1,10 @@
1
1
  require 'digest/sha1'
2
+ require 'securerandom'
2
3
 
3
4
  module Wechat
4
5
  module Ticket
5
6
  class JsapiBase
6
- attr_reader :client, :access_token, :jsapi_ticket_file, :access_ticket, :ticket_life_in_seconds, :got_ticket_at
7
+ attr_reader :client, :access_token, :oauth2_state, :jsapi_ticket_file, :access_ticket, :ticket_life_in_seconds, :got_ticket_at
7
8
 
8
9
  def initialize(client, access_token, jsapi_ticket_file)
9
10
  @client = client
@@ -47,6 +48,7 @@ module Wechat
47
48
  td = read_ticket
48
49
  @ticket_life_in_seconds = td.fetch('ticket_expires_in').to_i
49
50
  @got_ticket_at = td.fetch('got_ticket_at').to_i
51
+ @oauth2_state = td.fetch('oauth2_state')
50
52
  @access_ticket = td.fetch('ticket') # return access_ticket same time
51
53
  rescue JSON::ParserError, Errno::ENOENT, KeyError, TypeError
52
54
  refresh
@@ -5,6 +5,7 @@ module Wechat
5
5
  class PublicJsapiTicket < JsapiBase
6
6
  def refresh
7
7
  data = client.get('ticket/getticket', params: { access_token: access_token.token, type: 'jsapi' })
8
+ @oauth2_state = data['oauth2_state'] = SecureRandom.hex(16)
8
9
  write_ticket_to_store(data)
9
10
  read_ticket_from_store
10
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.8
4
+ version: 0.7.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Skinnyworm
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-03-31 00:00:00.000000000 Z
12
+ date: 2016-04-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -152,10 +152,10 @@ files:
152
152
  - lib/wechat/api_base.rb
153
153
  - lib/wechat/api_loader.rb
154
154
  - lib/wechat/cipher.rb
155
- - lib/wechat/client.rb
156
155
  - lib/wechat/controller_api.rb
157
156
  - lib/wechat/corp_api.rb
158
157
  - lib/wechat/helpers.rb
158
+ - lib/wechat/http_client.rb
159
159
  - lib/wechat/message.rb
160
160
  - lib/wechat/responder.rb
161
161
  - lib/wechat/signature.rb