line-bot 0.1.7
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/CONTRIBUTING.md +15 -0
- data/LICENSE +202 -0
- data/README.md +269 -0
- data/lib/line/bot.rb +14 -0
- data/lib/line/bot/api.rb +8 -0
- data/lib/line/bot/api/errors.rb +9 -0
- data/lib/line/bot/api/version.rb +7 -0
- data/lib/line/bot/builder/multiple_message.rb +101 -0
- data/lib/line/bot/builder/rich_message.rb +139 -0
- data/lib/line/bot/client.rb +303 -0
- data/lib/line/bot/event_type.rb +15 -0
- data/lib/line/bot/httpclient.rb +31 -0
- data/lib/line/bot/message.rb +9 -0
- data/lib/line/bot/message/audio.rb +25 -0
- data/lib/line/bot/message/base.rb +35 -0
- data/lib/line/bot/message/contact.rb +18 -0
- data/lib/line/bot/message/content_type.rb +16 -0
- data/lib/line/bot/message/image.rb +23 -0
- data/lib/line/bot/message/location.rb +28 -0
- data/lib/line/bot/message/recipient_type.rb +9 -0
- data/lib/line/bot/message/sticker.rb +26 -0
- data/lib/line/bot/message/text.rb +22 -0
- data/lib/line/bot/message/video.rb +23 -0
- data/lib/line/bot/operation.rb +3 -0
- data/lib/line/bot/operation/added_as_friend.rb +10 -0
- data/lib/line/bot/operation/base.rb +17 -0
- data/lib/line/bot/operation/blocked_account.rb +10 -0
- data/lib/line/bot/operation/op_type.rb +10 -0
- data/lib/line/bot/receive/message.rb +69 -0
- data/lib/line/bot/receive/operation.rb +42 -0
- data/lib/line/bot/receive/request.rb +35 -0
- data/lib/line/bot/request.rb +86 -0
- data/lib/line/bot/response/user/contact.rb +22 -0
- data/lib/line/bot/response/user/profile.rb +30 -0
- data/lib/line/bot/utils.rb +16 -0
- data/line-bot-api.gemspec +32 -0
- data/line-bot.gemspec +32 -0
- metadata +193 -0
data/lib/line/bot.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'line/bot/client'
|
2
|
+
require 'line/bot/api/errors'
|
3
|
+
require 'line/bot/api'
|
4
|
+
require 'line/bot/event_type'
|
5
|
+
require 'line/bot/message'
|
6
|
+
require 'line/bot/operation'
|
7
|
+
require 'line/bot/receive/message'
|
8
|
+
require 'line/bot/receive/operation'
|
9
|
+
require 'line/bot/receive/request'
|
10
|
+
require 'line/bot/response/user/profile'
|
11
|
+
require 'line/bot/request'
|
12
|
+
require 'line/bot/httpclient'
|
13
|
+
require 'line/bot/utils'
|
14
|
+
require 'line/bot/api/version'
|
data/lib/line/bot/api.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'line/bot/message'
|
2
|
+
|
3
|
+
module Line
|
4
|
+
module Bot
|
5
|
+
module Builder
|
6
|
+
class MultipleMessage
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@messages ||= []
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_text(attrs = {})
|
14
|
+
tap {
|
15
|
+
message = Message::Text.new(
|
16
|
+
text: attrs[:text],
|
17
|
+
)
|
18
|
+
push_message(message)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_image(attrs = {})
|
23
|
+
tap {
|
24
|
+
message = Message::Image.new(
|
25
|
+
image_url: attrs[:image_url],
|
26
|
+
preview_url: attrs[:preview_url],
|
27
|
+
)
|
28
|
+
push_message(message)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_video(attrs = {})
|
33
|
+
tap {
|
34
|
+
message = Message::Video.new(
|
35
|
+
video_url: attrs[:video_url],
|
36
|
+
preview_url: attrs[:preview_url],
|
37
|
+
)
|
38
|
+
push_message(message)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_audio(attrs = {})
|
43
|
+
tap {
|
44
|
+
message = Message::Audio.new(
|
45
|
+
audio_url: attrs[:audio_url],
|
46
|
+
duration: attrs[:duration],
|
47
|
+
)
|
48
|
+
push_message(message)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_location(attrs = {})
|
53
|
+
tap {
|
54
|
+
message = Message::Location.new(
|
55
|
+
title: attrs[:title],
|
56
|
+
latitude: attrs[:latitude],
|
57
|
+
longitude: attrs[:longitude],
|
58
|
+
)
|
59
|
+
push_message(message)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_sticker(attrs = {})
|
64
|
+
tap {
|
65
|
+
message = Message::Sticker.new(
|
66
|
+
stkpkgid: attrs[:stkpkgid],
|
67
|
+
stkid: attrs[:stkid],
|
68
|
+
stkver: attrs[:stkver],
|
69
|
+
)
|
70
|
+
push_message(message)
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def push_message(message)
|
75
|
+
raise ArgumentError, "Invalid argument: `message`" unless message.valid?
|
76
|
+
@messages << message.content
|
77
|
+
end
|
78
|
+
|
79
|
+
def send(attrs = {})
|
80
|
+
@client.send_message(attrs[:to_mid], self)
|
81
|
+
end
|
82
|
+
|
83
|
+
def event_type
|
84
|
+
Line::Bot::EventType::MULTIPLE_MESSAGE
|
85
|
+
end
|
86
|
+
|
87
|
+
def content
|
88
|
+
{
|
89
|
+
messageNotified: 0,
|
90
|
+
messages: @messages
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def valid?
|
95
|
+
@messages.size > 0
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'line/bot/message'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Line
|
5
|
+
module Bot
|
6
|
+
module Builder
|
7
|
+
class RichMessage
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@actions ||= {}
|
11
|
+
@listeners ||= []
|
12
|
+
@client = client
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_action(attrs = {})
|
16
|
+
tap {
|
17
|
+
attrs.each { |key, value|
|
18
|
+
raise ArgumentError, 'Invalid arguments, :text, :link_url keys.' unless validate_action_attributes(value)
|
19
|
+
|
20
|
+
@actions[key.to_s] = {
|
21
|
+
type: 'web',
|
22
|
+
text: value[:text].to_s,
|
23
|
+
params: {
|
24
|
+
linkUri: value[:link_url].to_s
|
25
|
+
},
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_action_attributes(attrs = {})
|
32
|
+
attrs[:text] && attrs[:link_url]
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_listener(attrs = {})
|
36
|
+
tap {
|
37
|
+
raise ArgumentError, 'Invalid arguments, :x, :y, :width, :height keys.' unless validate_listener_attributes(attrs)
|
38
|
+
|
39
|
+
listener = {
|
40
|
+
type: 'touch', # Fixed "touch".
|
41
|
+
params: [attrs[:x].to_i, attrs[:y].to_i, attrs[:width].to_i, attrs[:height].to_i],
|
42
|
+
action: attrs[:action].to_s,
|
43
|
+
}
|
44
|
+
@listeners << listener
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_listener_attributes(attrs = {})
|
49
|
+
attrs[:action] &&
|
50
|
+
attrs[:x] &&
|
51
|
+
attrs[:y] &&
|
52
|
+
attrs[:width] &&
|
53
|
+
attrs[:height]
|
54
|
+
end
|
55
|
+
|
56
|
+
# send rich message to line server and to users
|
57
|
+
#
|
58
|
+
# @param attrs [Hash]
|
59
|
+
# @param attrs [:to_mid] [String or Array] line user's mids
|
60
|
+
# @param attrs [:image_url] [String] image file's url
|
61
|
+
# @param attrs [:alt_text] [String] alt text for image file's url
|
62
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :image_url, :preview_url keys.
|
63
|
+
#
|
64
|
+
# @return [Net::HTTPResponse] response for a request to line server
|
65
|
+
def send(attrs = {})
|
66
|
+
@image_url = attrs[:image_url]
|
67
|
+
@alt_text = attrs[:alt_text]
|
68
|
+
|
69
|
+
@client.send_message(attrs[:to_mid], self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def height
|
73
|
+
height = 0
|
74
|
+
@listeners.each { |listener|
|
75
|
+
h = listener[:params][1] + listener[:params][3] # params.y + params.height
|
76
|
+
height = h if height < h
|
77
|
+
}
|
78
|
+
|
79
|
+
height > 2080 ? 2080 : height
|
80
|
+
end
|
81
|
+
|
82
|
+
def event_type
|
83
|
+
Line::Bot::EventType::MESSAGE
|
84
|
+
end
|
85
|
+
|
86
|
+
def content
|
87
|
+
{
|
88
|
+
contentType: Line::Bot::Message::ContentType::RICH_MESSAGE,
|
89
|
+
toType: Line::Bot::Message::RecipientType::USER,
|
90
|
+
contentMetadata: {
|
91
|
+
DOWNLOAD_URL: @image_url,
|
92
|
+
SPEC_REV: "1", # Fixed "1".
|
93
|
+
ALT_TEXT: @alt_text,
|
94
|
+
MARKUP_JSON: markup.to_json,
|
95
|
+
},
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid?
|
100
|
+
@image_url && @alt_text
|
101
|
+
end
|
102
|
+
|
103
|
+
def markup
|
104
|
+
{
|
105
|
+
canvas: {
|
106
|
+
height: height,
|
107
|
+
width: 1040, # Integer fixed value: 1040
|
108
|
+
initialScene: 'scene1',
|
109
|
+
},
|
110
|
+
images: {
|
111
|
+
image1: {
|
112
|
+
x: 0,
|
113
|
+
y: 0,
|
114
|
+
w: 1040, # Integer fixed value: 1040
|
115
|
+
h: height
|
116
|
+
},
|
117
|
+
},
|
118
|
+
actions: @actions,
|
119
|
+
scenes: {
|
120
|
+
scene1: {
|
121
|
+
draws: [
|
122
|
+
{
|
123
|
+
image: 'image1', # Use the image ID "image1".
|
124
|
+
x: 0,
|
125
|
+
y: 0,
|
126
|
+
w: 1040, # Integer fixed value: 1040
|
127
|
+
h: height
|
128
|
+
},
|
129
|
+
],
|
130
|
+
listeners: @listeners
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'line/bot/message'
|
2
|
+
require 'line/bot/request'
|
3
|
+
require 'line/bot/builder/rich_message'
|
4
|
+
require 'line/bot/builder/multiple_message'
|
5
|
+
require 'line/bot/api/errors'
|
6
|
+
require 'base64'
|
7
|
+
require 'net/http'
|
8
|
+
|
9
|
+
module Line
|
10
|
+
module Bot
|
11
|
+
class Client
|
12
|
+
|
13
|
+
include Line::Bot::Utils
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
attr_accessor :channel_id, :channel_secret, :channel_mid, :endpoint, :to_channel_id, :httpclient
|
17
|
+
|
18
|
+
# Initialize a new Bot Client.
|
19
|
+
#
|
20
|
+
# @param options [Hash]
|
21
|
+
#
|
22
|
+
# @return [Line::Bot::Client]
|
23
|
+
def initialize(options = {})
|
24
|
+
options.each do |key, value|
|
25
|
+
instance_variable_set("@#{key}", value)
|
26
|
+
end
|
27
|
+
yield(self) if block_given?
|
28
|
+
|
29
|
+
@httpclient ||= Line::Bot::HTTPClient.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def endpoint
|
33
|
+
@endpoint ||= Line::Bot::API::DEFAULT_ENDPOINT
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_channel_id
|
37
|
+
@to_channel_id ||= Line::Bot::API::DEFAULT_SENDING_MESSAGE_CHANNEL_ID
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Hash]
|
41
|
+
def credentials
|
42
|
+
{
|
43
|
+
'X-Line-ChannelID' => channel_id,
|
44
|
+
'X-Line-ChannelSecret' => channel_secret,
|
45
|
+
'X-Line-Trusted-User-With-ACL' => channel_mid,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def credentials?
|
50
|
+
credentials.values.all?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Send text to users.
|
54
|
+
#
|
55
|
+
# @param attrs [Hash]
|
56
|
+
# @param to_mid [String or Array] User's identifiers
|
57
|
+
# @param text [String]
|
58
|
+
#
|
59
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :text keys.
|
60
|
+
#
|
61
|
+
# @return [Net::HTTPResponse]
|
62
|
+
def send_text(attrs = {})
|
63
|
+
message = Message::Text.new(
|
64
|
+
text: attrs[:text],
|
65
|
+
)
|
66
|
+
send_message(attrs[:to_mid], message)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Send image to users.
|
70
|
+
#
|
71
|
+
# @param attrs [Hash]
|
72
|
+
# @param to_mid [String or Array] User's identifiers
|
73
|
+
# @param image_url [String] Image file's url
|
74
|
+
# @param preview_url [String] Preview image file's url
|
75
|
+
#
|
76
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :image_url, :preview_url keys.
|
77
|
+
#
|
78
|
+
# @return [Net::HTTPResponse]
|
79
|
+
def send_image(attrs = {})
|
80
|
+
message = Message::Image.new(
|
81
|
+
image_url: attrs[:image_url],
|
82
|
+
preview_url: attrs[:preview_url],
|
83
|
+
)
|
84
|
+
send_message(attrs[:to_mid], message)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Send video to users.
|
88
|
+
#
|
89
|
+
# @param attrs [Hash]
|
90
|
+
# @param to_mid [String or Array] User's identifiers
|
91
|
+
# @param video_url [String] Video file's url
|
92
|
+
# @param preview_url [String] Preview image file's url
|
93
|
+
#
|
94
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :video_url, :preview_url keys.
|
95
|
+
#
|
96
|
+
# @return [Net::HTTPResponse]
|
97
|
+
def send_video(attrs = {})
|
98
|
+
message = Message::Video.new(
|
99
|
+
video_url: attrs[:video_url],
|
100
|
+
preview_url: attrs[:preview_url],
|
101
|
+
)
|
102
|
+
send_message(attrs[:to_mid], message)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Send audio to users.
|
106
|
+
#
|
107
|
+
# @param attrs [Hash]
|
108
|
+
# @param to_mid [String or Array] User's identifiers
|
109
|
+
# @param audio_url [String] Audio file's url
|
110
|
+
# @param duration [String or Integer] Voice message's length, milliseconds
|
111
|
+
#
|
112
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :audio_url, :duration keys.
|
113
|
+
#
|
114
|
+
# @return [Net::HTTPResponse]
|
115
|
+
def send_audio(attrs = {})
|
116
|
+
message = Message::Audio.new(
|
117
|
+
audio_url: attrs[:audio_url],
|
118
|
+
duration: attrs[:duration],
|
119
|
+
)
|
120
|
+
send_message(attrs[:to_mid], message)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Send location to users.
|
124
|
+
#
|
125
|
+
# @param attrs [Hash]
|
126
|
+
# @param to_mid [String or Array] User's identifiers
|
127
|
+
# @param title [String] Location's title
|
128
|
+
# @param address [String] Location's address
|
129
|
+
# @param latitude [Float] Location's latitude
|
130
|
+
# @param longitude [Float] Location's longitude
|
131
|
+
#
|
132
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :title, :latitude, :longitude keys.
|
133
|
+
#
|
134
|
+
# @return [Net::HTTPResponse]
|
135
|
+
def send_location(attrs = {})
|
136
|
+
message = Message::Location.new(
|
137
|
+
title: attrs[:title],
|
138
|
+
address: attrs[:address],
|
139
|
+
latitude: attrs[:latitude],
|
140
|
+
longitude: attrs[:longitude],
|
141
|
+
)
|
142
|
+
send_message(attrs[:to_mid], message)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Send sticker to users.
|
146
|
+
#
|
147
|
+
# @param attrs [Hash]
|
148
|
+
# @param to_mid [String or Array] User's identifiers
|
149
|
+
# @param stkpkgid [String or Integer] Sticker's package identifier
|
150
|
+
# @param stkid [String or Integer] Sticker's identifier
|
151
|
+
# @param stkver [String or Integer] Sticker's version number
|
152
|
+
#
|
153
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :stkpkgid, :stkid, :stkver keys.
|
154
|
+
#
|
155
|
+
# @return [Net::HTTPResponse]
|
156
|
+
def send_sticker(attrs = {})
|
157
|
+
message = Message::Sticker.new(
|
158
|
+
stkpkgid: attrs[:stkpkgid],
|
159
|
+
stkid: attrs[:stkid],
|
160
|
+
stkver: attrs[:stkver],
|
161
|
+
)
|
162
|
+
send_message(attrs[:to_mid], message)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Send message to line server and to users.
|
166
|
+
#
|
167
|
+
# @param to_mid [String or Array] User's identifiers
|
168
|
+
# @param message [Line::Bot::Message]
|
169
|
+
#
|
170
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing message.
|
171
|
+
#
|
172
|
+
# @return [Net::HTTPResponse]
|
173
|
+
def send_message(to_mid, message)
|
174
|
+
raise Line::Bot::API::InvalidCredentialsError, 'Invalidates credentials' unless credentials?
|
175
|
+
|
176
|
+
request = Request.new do |config|
|
177
|
+
config.to_channel_id = to_channel_id
|
178
|
+
config.httpclient = httpclient
|
179
|
+
config.endpoint = endpoint
|
180
|
+
config.endpoint_path = '/events'
|
181
|
+
config.credentials = credentials
|
182
|
+
config.to_mid = to_mid
|
183
|
+
config.message = message
|
184
|
+
end
|
185
|
+
|
186
|
+
request.post
|
187
|
+
end
|
188
|
+
|
189
|
+
# Get message content.
|
190
|
+
#
|
191
|
+
# @param identifier [String] Message's identifier
|
192
|
+
#
|
193
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing message.
|
194
|
+
#
|
195
|
+
# @return [Net::HTTPResponse]
|
196
|
+
def get_message_content(identifier)
|
197
|
+
endpoint_path = "/bot/message/#{identifier}/content"
|
198
|
+
get(endpoint_path)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get preview of message content.
|
202
|
+
#
|
203
|
+
# @param identifier [String] Message's identifier
|
204
|
+
#
|
205
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing message.
|
206
|
+
#
|
207
|
+
# @return [Net::HTTPResponse]
|
208
|
+
def get_message_content_preview(identifier)
|
209
|
+
endpoint_path = "/bot/message/#{identifier}/content/preview"
|
210
|
+
get(endpoint_path)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get user profile.
|
214
|
+
#
|
215
|
+
# @param mids [String or Array] User's identifiers
|
216
|
+
#
|
217
|
+
# @raise [ArgumentError] Error raised when supplied argument are missing message.
|
218
|
+
# @raise [HTTPError]
|
219
|
+
#
|
220
|
+
# @return [Line::Bot::Response::User::Profile]
|
221
|
+
def get_user_profile(mids)
|
222
|
+
raise ArgumentError, 'Wrong argument type `mids`' unless validate_mids(mids)
|
223
|
+
|
224
|
+
query = mids.is_a?(Array) ? mids.join(',') : mids
|
225
|
+
endpoint_path = "/profiles?mids=#{query}"
|
226
|
+
|
227
|
+
response = get(endpoint_path)
|
228
|
+
|
229
|
+
Line::Bot::Response::User::Profile.new(response) if !response.value
|
230
|
+
end
|
231
|
+
|
232
|
+
# Fetch data, get content of specified URL.
|
233
|
+
#
|
234
|
+
# @param endpoint_path [String]
|
235
|
+
#
|
236
|
+
# @return [Net::HTTPResponse]
|
237
|
+
def get(endpoint_path)
|
238
|
+
raise Line::Bot::API::InvalidCredentialsError, 'Invalidates credentials' unless credentials?
|
239
|
+
|
240
|
+
request = Request.new do |config|
|
241
|
+
config.to_channel_id = to_channel_id
|
242
|
+
config.httpclient = httpclient
|
243
|
+
config.endpoint = endpoint
|
244
|
+
config.endpoint_path = endpoint_path
|
245
|
+
config.credentials = credentials
|
246
|
+
end
|
247
|
+
|
248
|
+
request.get
|
249
|
+
end
|
250
|
+
|
251
|
+
# Create rich message to line server and to users.
|
252
|
+
#
|
253
|
+
# @return [Line::Bot::Builder::RichMessage]
|
254
|
+
def rich_message
|
255
|
+
Line::Bot::Builder::RichMessage.new(self)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Create multiple message to line server and to users.
|
259
|
+
#
|
260
|
+
# @return [Line::Bot::Builder::MultipleMessage]
|
261
|
+
def multiple_message
|
262
|
+
Line::Bot::Builder::MultipleMessage.new(self)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Validate signature
|
266
|
+
#
|
267
|
+
# @param content [String] Request's body
|
268
|
+
# @param channel_signature [String] Request'header 'X-LINE-ChannelSignature' # HTTP_X_LINE_CHANNELSIGNATURE
|
269
|
+
#
|
270
|
+
# @return [Boolean]
|
271
|
+
def validate_signature(content = "", channel_signature)
|
272
|
+
return false unless !channel_signature.nil? && credentials?
|
273
|
+
|
274
|
+
hash = OpenSSL::HMAC::digest(OpenSSL::Digest::SHA256.new, channel_secret, content)
|
275
|
+
signature = Base64.strict_encode64(hash)
|
276
|
+
|
277
|
+
variable_secure_compare(channel_signature, signature)
|
278
|
+
end
|
279
|
+
|
280
|
+
private
|
281
|
+
# Constant time string comparison.
|
282
|
+
#
|
283
|
+
# via timing attacks.
|
284
|
+
# reference: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/security_utils.rb
|
285
|
+
# @return [Boolean]
|
286
|
+
def variable_secure_compare(a, b)
|
287
|
+
secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
|
288
|
+
end
|
289
|
+
|
290
|
+
# @return [Boolean]
|
291
|
+
def secure_compare(a, b)
|
292
|
+
return false unless a.bytesize == b.bytesize
|
293
|
+
|
294
|
+
l = a.unpack "C#{a.bytesize}"
|
295
|
+
|
296
|
+
res = 0
|
297
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
298
|
+
res == 0
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|