twilio-ruby 5.0.0.rc10 → 5.0.0.rc11
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 +4 -4
- data/lib/rack/twilio_webhook_authentication.rb +3 -3
- data/lib/twilio-ruby/jwt/access_token.rb +111 -0
- data/lib/twilio-ruby/{util → jwt}/capability.rb +9 -9
- data/lib/twilio-ruby/jwt/task_router.rb +203 -0
- data/lib/twilio-ruby/{util → security}/request_validator.rb +1 -1
- data/lib/twilio-ruby/version.rb +1 -1
- data/lib/twilio-ruby.rb +11 -13
- data/spec/jwt/access_token_spec.rb +114 -0
- data/spec/{util → jwt}/capability_spec.rb +18 -18
- data/spec/jwt/task_router_spec.rb +110 -0
- data/spec/jwt/task_router_taskqueue_spec.rb +111 -0
- data/spec/jwt/task_router_worker_spec.rb +146 -0
- data/spec/jwt/task_router_workspace_spec.rb +110 -0
- data/spec/rack/twilio_webhook_authentication_spec.rb +7 -7
- data/spec/{util → security}/request_validator_spec.rb +5 -5
- data/twilio-ruby.gemspec +0 -1
- metadata +19 -31
- data/lib/twilio-ruby/task_router/capability.rb +0 -87
- data/lib/twilio-ruby/task_router.rb +0 -0
- data/lib/twilio-ruby/util/access_token.rb +0 -70
- data/lib/twilio-ruby/util/client_config.rb +0 -29
- data/spec/task_router_spec.rb +0 -114
- data/spec/util/access_token_spec.rb +0 -103
- data/spec/util/client_config_spec.rb +0 -21
@@ -1,87 +0,0 @@
|
|
1
|
-
module Twilio
|
2
|
-
module TaskRouter
|
3
|
-
class Capability
|
4
|
-
|
5
|
-
TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com'
|
6
|
-
TASK_ROUTER_VERSION = 'v1'
|
7
|
-
TASK_ROUTER_WEBSOCKET_BASE_URL = 'https://event-bridge.twilio.com/v1/wschannels'
|
8
|
-
|
9
|
-
REQUIRED = {required: true}
|
10
|
-
OPTIONAL = {required: false}
|
11
|
-
|
12
|
-
def initialize(account_sid, auth_token, workspace_sid, worker_sid)
|
13
|
-
@account_sid = account_sid
|
14
|
-
@auth_token = auth_token
|
15
|
-
@workspace_sid = workspace_sid
|
16
|
-
@worker_sid = worker_sid
|
17
|
-
@policies = []
|
18
|
-
allow_websocket_requests
|
19
|
-
allow_activity_list_fetch
|
20
|
-
end
|
21
|
-
|
22
|
-
def workspace_url
|
23
|
-
"#{TASK_ROUTER_BASE_URL}/#{TASK_ROUTER_VERSION}/Workspaces/#{@workspace_sid}"
|
24
|
-
end
|
25
|
-
|
26
|
-
def worker_url
|
27
|
-
"#{workspace_url}/Workers/#{@worker_sid}"
|
28
|
-
end
|
29
|
-
|
30
|
-
def allow_worker_activity_updates
|
31
|
-
add_policy(worker_url, "POST", nil, {ActivitySid: REQUIRED})
|
32
|
-
end
|
33
|
-
|
34
|
-
def allow_worker_fetch_attributes
|
35
|
-
add_policy(worker_url, "GET")
|
36
|
-
end
|
37
|
-
|
38
|
-
def allow_task_reservation_updates
|
39
|
-
task_url = "#{workspace_url}/Tasks/**"
|
40
|
-
add_policy(task_url, "POST", nil, {ReservationStatus: REQUIRED})
|
41
|
-
end
|
42
|
-
|
43
|
-
def add_policy(url, method, query_filters = nil, post_filters = nil, allowed = true)
|
44
|
-
policy = {
|
45
|
-
url: url,
|
46
|
-
method: method,
|
47
|
-
query_filter: query_filters || {},
|
48
|
-
post_filter: post_filters || {},
|
49
|
-
allow: allowed
|
50
|
-
}
|
51
|
-
|
52
|
-
@policies.push(policy)
|
53
|
-
end
|
54
|
-
|
55
|
-
def generate_token(ttl = 3600)
|
56
|
-
payload = {
|
57
|
-
iss: @account_sid,
|
58
|
-
exp: (Time.now.to_i + ttl),
|
59
|
-
version: @version,
|
60
|
-
friendly_name: @worker_sid,
|
61
|
-
policies: @policies,
|
62
|
-
account_sid: @account_sid,
|
63
|
-
worker_sid: @worker_sid,
|
64
|
-
channel: @worker_sid,
|
65
|
-
workspace_sid: @workspace_sid
|
66
|
-
}
|
67
|
-
|
68
|
-
JWT.encode payload, @auth_token
|
69
|
-
end
|
70
|
-
|
71
|
-
protected
|
72
|
-
|
73
|
-
def allow_websocket_requests
|
74
|
-
worker_url = "#{TASK_ROUTER_WEBSOCKET_BASE_URL}/#{@account_sid}/#{@worker_sid}"
|
75
|
-
['GET', 'POST'].each do |meth|
|
76
|
-
add_policy(worker_url, meth)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def allow_activity_list_fetch
|
81
|
-
url = "#{workspace_url}/Activities"
|
82
|
-
add_policy(url, 'GET')
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
File without changes
|
@@ -1,70 +0,0 @@
|
|
1
|
-
module Twilio
|
2
|
-
module Util
|
3
|
-
class AccessToken
|
4
|
-
def initialize(signing_key_id, account_sid, secret, ttl=3600)
|
5
|
-
@signing_key_sid = signing_key_id
|
6
|
-
@account_sid = account_sid
|
7
|
-
@secret = secret
|
8
|
-
@ttl = ttl
|
9
|
-
@grants = []
|
10
|
-
end
|
11
|
-
|
12
|
-
def add_grant(resource, actions=Action::ALL)
|
13
|
-
actions = [*actions]
|
14
|
-
@grants.push({"res" => resource, "act" => actions})
|
15
|
-
self
|
16
|
-
end
|
17
|
-
|
18
|
-
def add_endpoint_grant(endpoint, actions=[Action::Client::LISTEN, Action::Client::INVITE])
|
19
|
-
resource = "sip:#{endpoint}@#{@account_sid}.endpoint.twilio.com"
|
20
|
-
add_grant resource, actions
|
21
|
-
end
|
22
|
-
|
23
|
-
def add_rest_grant(uri, actions=Action::ALL)
|
24
|
-
uri = uri[1..-1] if uri[0] == '/'
|
25
|
-
resource = "https://api.twilio.com/2010-04-01/Accounts/#{@account_sid}/#{uri}"
|
26
|
-
add_grant resource, actions
|
27
|
-
end
|
28
|
-
|
29
|
-
def enable_nts()
|
30
|
-
add_rest_grant('/Tokens', Action::HTTP::POST)
|
31
|
-
end
|
32
|
-
|
33
|
-
def as_jwt()
|
34
|
-
now = Time.now.to_i - 1
|
35
|
-
headers = {
|
36
|
-
'cty' => 'twilio-sat;v=1'
|
37
|
-
}
|
38
|
-
payload = {
|
39
|
-
'jti' => "#{@signing_key_sid}-#{now}",
|
40
|
-
'iss' => @signing_key_sid,
|
41
|
-
'sub' => @account_sid,
|
42
|
-
'nbf' => now,
|
43
|
-
'exp' => now + @ttl,
|
44
|
-
'grants' => @grants
|
45
|
-
}
|
46
|
-
JWT.encode payload, @secret, 'HS256', headers
|
47
|
-
end
|
48
|
-
|
49
|
-
def to_s
|
50
|
-
as_jwt
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
class Action
|
55
|
-
ALL='*'
|
56
|
-
|
57
|
-
class HTTP
|
58
|
-
DELETE='DELETE'
|
59
|
-
GET='GET'
|
60
|
-
POST='POST'
|
61
|
-
PUT='PUT'
|
62
|
-
end
|
63
|
-
|
64
|
-
class Client
|
65
|
-
LISTEN='listen'
|
66
|
-
INVITE='invite'
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Twilio
|
2
|
-
module Util
|
3
|
-
class ClientConfig
|
4
|
-
DEFAULTS = {
|
5
|
-
host: 'api.twilio.com',
|
6
|
-
port: 443,
|
7
|
-
use_ssl: true,
|
8
|
-
ssl_verify_peer: true,
|
9
|
-
ssl_ca_file: File.dirname(__FILE__) + '/../../../conf/cacert.pem',
|
10
|
-
timeout: 30,
|
11
|
-
proxy_addr: nil,
|
12
|
-
proxy_port: nil,
|
13
|
-
proxy_user: nil,
|
14
|
-
proxy_pass: nil,
|
15
|
-
retry_limit: 1
|
16
|
-
}
|
17
|
-
|
18
|
-
DEFAULTS.each_key do |attribute|
|
19
|
-
attr_accessor attribute
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(opts={})
|
23
|
-
DEFAULTS.each do |attribute, value|
|
24
|
-
send("#{attribute}=".to_sym, opts.fetch(attribute, value))
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/spec/task_router_spec.rb
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Twilio::TaskRouter::Capability do
|
4
|
-
describe 'with a capability' do
|
5
|
-
before :each do
|
6
|
-
@capability = Twilio::TaskRouter::Capability.new 'AC123', 'foobar', 'WS456', 'WK789'
|
7
|
-
end
|
8
|
-
|
9
|
-
it 'should return a valid jwt when #generate_token is called' do
|
10
|
-
token = @capability.generate_token
|
11
|
-
decoded, header = JWT.decode token, 'foobar'
|
12
|
-
expect(decoded['policies']).not_to be_nil
|
13
|
-
expect(decoded['iss']).not_to be_nil
|
14
|
-
expect(decoded['exp']).not_to be_nil
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'should properly set the iss key in the payload' do
|
18
|
-
token = @capability.generate_token
|
19
|
-
decoded, header = JWT.decode token, 'foobar'
|
20
|
-
expect(decoded['iss']).to eq('AC123')
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'should properly set exp based on the default 1-hour ttl' do
|
24
|
-
seconds = Time.now.to_i
|
25
|
-
token = @capability.generate_token
|
26
|
-
decoded, header = JWT.decode token, 'foobar'
|
27
|
-
expect(decoded['exp']).to eq(seconds + 3600)
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'should properly set exp based on the ttl arg to #generate_token' do
|
31
|
-
seconds = Time.now.to_i
|
32
|
-
ttl = rand 10000
|
33
|
-
token = @capability.generate_token ttl
|
34
|
-
decoded, header = JWT.decode token, 'foobar'
|
35
|
-
expect(decoded['exp']).to eq(seconds + ttl)
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'should allow websocket operations and activity list fetches by default' do
|
39
|
-
token = @capability.generate_token
|
40
|
-
decoded, header = JWT.decode token, 'foobar'
|
41
|
-
expect(decoded['policies'].size).to eq(3)
|
42
|
-
get_policy = {
|
43
|
-
"url" => 'https://event-bridge.twilio.com/v1/wschannels/AC123/WK789',
|
44
|
-
"method" => 'GET',
|
45
|
-
"query_filter" => {},
|
46
|
-
"post_filter" => {},
|
47
|
-
"allow" => true
|
48
|
-
}
|
49
|
-
expect(decoded['policies'][0]).to eq(get_policy)
|
50
|
-
post_policy = {
|
51
|
-
"url" => 'https://event-bridge.twilio.com/v1/wschannels/AC123/WK789',
|
52
|
-
"method" => 'POST',
|
53
|
-
"query_filter" => {},
|
54
|
-
"post_filter" => {},
|
55
|
-
"allow" => true
|
56
|
-
}
|
57
|
-
expect(decoded['policies'][1]).to eq(post_policy)
|
58
|
-
|
59
|
-
activities_policy = {
|
60
|
-
'url' => 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities',
|
61
|
-
'method' => 'GET',
|
62
|
-
'query_filter' => {},
|
63
|
-
'post_filter' => {},
|
64
|
-
'allow' => true
|
65
|
-
}
|
66
|
-
expect(decoded['policies'][2]).to eq(activities_policy)
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'should add a policy when #allow_worker_activity_updates is called' do
|
70
|
-
@capability.allow_worker_activity_updates
|
71
|
-
token = @capability.generate_token
|
72
|
-
decoded, header = JWT.decode token, 'foobar'
|
73
|
-
expect(decoded['policies'].size).to eq(4)
|
74
|
-
activity_policy = {
|
75
|
-
'url' => 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789',
|
76
|
-
'method' => 'POST',
|
77
|
-
'query_filter' => {},
|
78
|
-
'post_filter' => {'ActivitySid' => {'required' => true}},
|
79
|
-
'allow' => true
|
80
|
-
}
|
81
|
-
expect(decoded['policies'][-1]).to eq(activity_policy)
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'should add a policy when #allow_worker_fetch_attributes is called' do
|
85
|
-
@capability.allow_worker_fetch_attributes
|
86
|
-
token = @capability.generate_token
|
87
|
-
decoded, header = JWT.decode token, 'foobar'
|
88
|
-
expect(decoded['policies'].size).to eq(4)
|
89
|
-
worker_policy = {
|
90
|
-
'url' => 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789',
|
91
|
-
'method' => 'GET',
|
92
|
-
'query_filter' => {},
|
93
|
-
'post_filter' => {},
|
94
|
-
'allow' => true
|
95
|
-
}
|
96
|
-
expect(decoded['policies'][-1]).to eq(worker_policy)
|
97
|
-
end
|
98
|
-
|
99
|
-
it 'should add a policy when #allow_task_reservation_updates is called' do
|
100
|
-
@capability.allow_task_reservation_updates
|
101
|
-
token = @capability.generate_token
|
102
|
-
decoded, header = JWT.decode token, 'foobar'
|
103
|
-
expect(decoded['policies'].size).to eq(4)
|
104
|
-
task_policy = {
|
105
|
-
'url' => 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**',
|
106
|
-
'method' => 'POST',
|
107
|
-
'query_filter' => {},
|
108
|
-
'post_filter' => {'ReservationStatus' => {'required' => true}},
|
109
|
-
'allow' => true
|
110
|
-
}
|
111
|
-
expect(decoded['policies'][-1]).to eq(task_policy)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
@@ -1,103 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Twilio::Util::AccessToken do
|
4
|
-
|
5
|
-
it 'should generate a token for no grants' do
|
6
|
-
scat = Twilio::Util::AccessToken.new 'SK123', 'AC123', 'secret'
|
7
|
-
token = scat.to_s
|
8
|
-
expect(token).not_to be_nil
|
9
|
-
payload, header = JWT.decode token, 'secret'
|
10
|
-
|
11
|
-
expect(payload['iss']).to eq('SK123')
|
12
|
-
expect(payload['sub']).to eq('AC123')
|
13
|
-
expect(payload['nbf']).not_to be_nil
|
14
|
-
expect(payload['exp']).not_to be_nil
|
15
|
-
expect(payload['nbf'] + 3600).to eq(payload['exp'])
|
16
|
-
expect(payload['jti']).not_to be_nil
|
17
|
-
expect("#{payload['iss']}-#{payload['nbf']}").to eq(payload['jti'])
|
18
|
-
expect(payload['grants']).not_to be_nil
|
19
|
-
expect(payload['grants'].count).to eq(0)
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'should be able to add standard grants' do
|
23
|
-
scat = Twilio::Util::AccessToken.new 'SK123', 'AC123', 'secret'
|
24
|
-
scat.add_grant 'https://api.twilio.com/**'
|
25
|
-
token = scat.to_s
|
26
|
-
expect(token).not_to be_nil
|
27
|
-
payload, header = JWT.decode token, 'secret'
|
28
|
-
|
29
|
-
expect(payload['iss']).to eq('SK123')
|
30
|
-
expect(payload['sub']).to eq('AC123')
|
31
|
-
expect(payload['nbf']).not_to be_nil
|
32
|
-
expect(payload['exp']).not_to be_nil
|
33
|
-
expect(payload['nbf'] + 3600).to eq(payload['exp'])
|
34
|
-
expect(payload['jti']).not_to be_nil
|
35
|
-
expect("#{payload['iss']}-#{payload['nbf']}").to eq(payload['jti'])
|
36
|
-
expect(payload['grants']).not_to be_nil
|
37
|
-
expect(payload['grants'].count).to eq(1)
|
38
|
-
expect(payload['grants'][0]['res']).to eq('https://api.twilio.com/**')
|
39
|
-
expect(payload['grants'][0]['act']).to eq(['*'])
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'should be able to add endpoint grants' do
|
43
|
-
scat = Twilio::Util::AccessToken.new 'SK123', 'AC123', 'secret'
|
44
|
-
scat.add_endpoint_grant 'bob'
|
45
|
-
token = scat.to_s
|
46
|
-
expect(token).not_to be_nil
|
47
|
-
payload, header = JWT.decode token, 'secret'
|
48
|
-
|
49
|
-
expect(payload['iss']).to eq('SK123')
|
50
|
-
expect(payload['sub']).to eq('AC123')
|
51
|
-
expect(payload['nbf']).not_to be_nil
|
52
|
-
expect(payload['exp']).not_to be_nil
|
53
|
-
expect(payload['nbf'] + 3600).to eq(payload['exp'])
|
54
|
-
expect(payload['jti']).not_to be_nil
|
55
|
-
expect("#{payload['iss']}-#{payload['nbf']}").to eq(payload['jti'])
|
56
|
-
expect(payload['grants']).not_to be_nil
|
57
|
-
expect(payload['grants'].count).to eq(1)
|
58
|
-
expect(payload['grants'][0]['res']).to eq('sip:bob@AC123.endpoint.twilio.com')
|
59
|
-
expect(payload['grants'][0]['act']).to eq([Twilio::Util::Action::Client::LISTEN, Twilio::Util::Action::Client::INVITE])
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'should be able to add rest grants' do
|
63
|
-
scat = Twilio::Util::AccessToken.new 'SK123', 'AC123', 'secret'
|
64
|
-
scat.add_rest_grant 'Apps'
|
65
|
-
token = scat.to_s
|
66
|
-
expect(token).not_to be_nil
|
67
|
-
payload, header = JWT.decode token, 'secret'
|
68
|
-
|
69
|
-
expect(payload['iss']).to eq('SK123')
|
70
|
-
expect(payload['sub']).to eq('AC123')
|
71
|
-
expect(payload['nbf']).not_to be_nil
|
72
|
-
expect(payload['exp']).not_to be_nil
|
73
|
-
expect(payload['nbf'] + 3600).to eq(payload['exp'])
|
74
|
-
expect(payload['jti']).not_to be_nil
|
75
|
-
expect("#{payload['iss']}-#{payload['nbf']}").to eq(payload['jti'])
|
76
|
-
expect(payload['grants']).not_to be_nil
|
77
|
-
expect(payload['grants'].count).to eq(1)
|
78
|
-
expect(payload['grants'][0]['res']).to eq('https://api.twilio.com/2010-04-01/Accounts/AC123/Apps')
|
79
|
-
expect(payload['grants'][0]['act']).to eq([Twilio::Util::Action::ALL])
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'should be able to enable NTS' do
|
83
|
-
scat = Twilio::Util::AccessToken.new 'SK123', 'AC123', 'secret'
|
84
|
-
scat.enable_nts
|
85
|
-
token = scat.to_s
|
86
|
-
expect(token).not_to be_nil
|
87
|
-
payload, header = JWT.decode token, 'secret'
|
88
|
-
|
89
|
-
expect(payload['iss']).to eq('SK123')
|
90
|
-
expect(payload['sub']).to eq('AC123')
|
91
|
-
expect(payload['nbf']).not_to be_nil
|
92
|
-
expect(payload['exp']).not_to be_nil
|
93
|
-
expect(payload['nbf'] + 3600).to eq(payload['exp'])
|
94
|
-
expect(payload['jti']).not_to be_nil
|
95
|
-
expect("#{payload['iss']}-#{payload['nbf']}").to eq(payload['jti'])
|
96
|
-
expect(payload['grants']).not_to be_nil
|
97
|
-
expect(payload['grants'].count).to eq(1)
|
98
|
-
expect(payload['grants'][0]['res']).to eq('https://api.twilio.com/2010-04-01/Accounts/AC123/Tokens')
|
99
|
-
expect(payload['grants'][0]['act']).to eq([Twilio::Util::Action::HTTP::POST])
|
100
|
-
end
|
101
|
-
|
102
|
-
|
103
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Twilio::Util::ClientConfig do
|
4
|
-
Twilio::Util::ClientConfig::DEFAULTS.each do |attribute, value|
|
5
|
-
it "sets an attribute with a default value" do
|
6
|
-
config = Twilio::Util::ClientConfig.new
|
7
|
-
expect(config.send(attribute)).to eq(value)
|
8
|
-
end
|
9
|
-
|
10
|
-
it "can update the value for the attribute" do
|
11
|
-
config = Twilio::Util::ClientConfig.new
|
12
|
-
config.send("#{attribute}=", "blah")
|
13
|
-
expect(config.send(attribute)).to eq("blah")
|
14
|
-
end
|
15
|
-
|
16
|
-
it "can set the value from a hash in the initializer" do
|
17
|
-
config = Twilio::Util::ClientConfig.new(attribute => 'blah')
|
18
|
-
expect(config.send(attribute)).to eq("blah")
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|