jicksta-adhearsion 0.8.0 → 0.8.2
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.
- data/EVENTS +2 -2
- data/adhearsion.gemspec +9 -8
- data/app_generators/ahn/ahn_generator.rb +6 -2
- data/app_generators/ahn/templates/Rakefile +2 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml +34 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +263 -0
- data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +108 -0
- data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml +2 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +1 -1
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/{config.yml → stomp_gateway.yml} +1 -1
- data/app_generators/ahn/templates/events.rb +1 -1
- data/lib/adhearsion/cli.rb +1 -1
- data/lib/adhearsion/component_manager.rb +1 -2
- data/lib/adhearsion/initializer/database.rb +2 -1
- data/lib/adhearsion/initializer/rails.rb +2 -1
- data/lib/adhearsion/version.rb +1 -1
- data/lib/adhearsion/voip/asterisk/agi_server.rb +3 -3
- metadata +9 -8
- data/README.txt +0 -5
data/EVENTS
CHANGED
data/adhearsion.gemspec
CHANGED
@@ -4,14 +4,16 @@ ADHEARSION_FILES = %w{
|
|
4
4
|
app_generators/ahn/templates/.ahnrc
|
5
5
|
app_generators/ahn/templates/components/ami_remote/ami_remote.rb
|
6
6
|
app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE
|
7
|
-
app_generators/ahn/templates/components/disabled/stomp_gateway/
|
7
|
+
app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.yml
|
8
8
|
app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown
|
9
9
|
app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb
|
10
|
-
app_generators/ahn/templates/components/
|
11
|
-
app_generators/ahn/templates/components/
|
12
|
-
app_generators/ahn/templates/components/restful_rpc/
|
13
|
-
app_generators/ahn/templates/components/restful_rpc/
|
14
|
-
app_generators/ahn/templates/components/restful_rpc/
|
10
|
+
app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb
|
11
|
+
app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml
|
12
|
+
app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml
|
13
|
+
app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb
|
14
|
+
app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown
|
15
|
+
app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb
|
16
|
+
app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb
|
15
17
|
app_generators/ahn/templates/components/simon_game/simon_game.rb
|
16
18
|
app_generators/ahn/templates/config/startup.rb
|
17
19
|
app_generators/ahn/templates/dialplan.rb
|
@@ -106,12 +108,11 @@ ADHEARSION_FILES = %w{
|
|
106
108
|
lib/theatre/version.rb
|
107
109
|
LICENSE
|
108
110
|
Rakefile
|
109
|
-
README.txt
|
110
111
|
}
|
111
112
|
|
112
113
|
Gem::Specification.new do |s|
|
113
114
|
s.name = "adhearsion"
|
114
|
-
s.version = "0.8.
|
115
|
+
s.version = "0.8.2"
|
115
116
|
|
116
117
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
117
118
|
s.authors = ["Jay Phillips"]
|
@@ -28,15 +28,18 @@ class AhnGenerator < RubiGen::Base
|
|
28
28
|
m.file *["components/ami_remote/ami_remote.rb"]*2
|
29
29
|
|
30
30
|
m.file *["components/disabled/stomp_gateway/stomp_gateway.rb"]*2
|
31
|
-
m.file *["components/disabled/stomp_gateway/
|
31
|
+
m.file *["components/disabled/stomp_gateway/stomp_gateway.yml"]*2
|
32
32
|
m.file *["components/disabled/stomp_gateway/README.markdown"]*2
|
33
33
|
|
34
34
|
m.file *["components/disabled/restful_rpc/restful_rpc.rb"]*2
|
35
|
-
m.file *["components/disabled/restful_rpc/
|
35
|
+
m.file *["components/disabled/restful_rpc/restful_rpc.yml"]*2
|
36
36
|
m.file *["components/disabled/restful_rpc/README.markdown"]*2
|
37
37
|
m.file *["components/disabled/restful_rpc/example-client.rb"]*2
|
38
38
|
m.file *["components/disabled/restful_rpc/spec/restful_rpc_spec.rb"]*2
|
39
39
|
|
40
|
+
m.file *["components/disabled/sandbox/sandbox.yml"]*2
|
41
|
+
m.file *["components/disabled/sandbox/sandbox.rb"]*2
|
42
|
+
|
40
43
|
m.file *["config/startup.rb"]*2
|
41
44
|
m.file *["dialplan.rb"]*2
|
42
45
|
m.file *["events.rb"]*2
|
@@ -80,6 +83,7 @@ EOS
|
|
80
83
|
BASEDIRS = %w(
|
81
84
|
components/simon_game
|
82
85
|
components/disabled/stomp_gateway
|
86
|
+
components/disabled/sandbox
|
83
87
|
components/ami_remote
|
84
88
|
components/disabled/restful_rpc/spec
|
85
89
|
config
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Adhearsion RESTful RPC Component
|
2
|
+
================================
|
3
|
+
|
4
|
+
This is a component for people want to integrate their telephony systems with non-Ruby systems. When enabled, this component
|
5
|
+
will start up a HTTP server within the Adhearsion process and accept POST requests to invoke Ruby methods shared in the
|
6
|
+
`methods_for(:rpc)` context.
|
7
|
+
|
8
|
+
Protocol Notes
|
9
|
+
--------------
|
10
|
+
|
11
|
+
When POSTing your data to.
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# You must have the "rest-client" and "json" gems installed for this file to work.
|
6
|
+
|
7
|
+
class RESTfulAdhearsion
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
# Note: :user and :password are non-existent by default
|
11
|
+
:host => "localhost",
|
12
|
+
:port => "5000",
|
13
|
+
:path_nesting => "/"
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(options={})
|
17
|
+
@options = DEFAULT_OPTIONS.merge options
|
18
|
+
|
19
|
+
@path_nesting = @options.delete :path_nesting
|
20
|
+
@host = @options.delete :host
|
21
|
+
@port = @options.delete :port
|
22
|
+
|
23
|
+
@url_beginning = "http://#{@host}:#{@port}#{@path_nesting}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method_name, *args)
|
27
|
+
JSON.parse RestClient::Resource.new(@url_beginning + method_name.to_s, @options).post(args.to_json)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
Adhearsion = RESTfulAdhearsion.new :host => "localhost", :port => 5000, :user => "jicksta", :password => "roflcopterz"
|
33
|
+
|
34
|
+
# ### Sample component code. Try doing "ahn create component testing123" and pasting this code in.
|
35
|
+
#
|
36
|
+
# methods_for :rpc do
|
37
|
+
# def i_like_hashes(options={})
|
38
|
+
# options.has_key?(:foo)
|
39
|
+
# end
|
40
|
+
# def i_like_arrays(*args)
|
41
|
+
# args.reverse
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
|
45
|
+
# Note: everything returned will be wrapped in an Array
|
46
|
+
|
47
|
+
p Adhearsion.i_like_hashes(:foo => "bar")
|
48
|
+
p Adhearsion.i_like_arrays(1,2,3,4,5)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Don't you love regular expressions? Matches only 0-255 octets. Recognizes "*" as an octet wildcard.
|
5
|
+
VALID_IP_ADDRESS = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)$/
|
6
|
+
|
7
|
+
def ip_allowed?(ip)
|
8
|
+
raise ArgumentError, "#{ip.inspect} is not a valid IP address!" unless ip.kind_of?(String) && ip =~ VALID_IP_ADDRESS
|
9
|
+
|
10
|
+
octets = ip.split "."
|
11
|
+
|
12
|
+
case COMPONENTS.restful_rpc["access"]
|
13
|
+
when "everyone"
|
14
|
+
true
|
15
|
+
when "whitelist"
|
16
|
+
whitelist = COMPONENTS.restful_rpc["whitelist"]
|
17
|
+
!! whitelist.find do |pattern|
|
18
|
+
pattern_octets = pattern.split "."
|
19
|
+
# Traverse both arrays in parallel
|
20
|
+
octets.zip(pattern_octets).map do |octet, octet_pattern|
|
21
|
+
octet_pattern == "*" ? true : (octet == octet_pattern)
|
22
|
+
end == [true, true, true, true]
|
23
|
+
end
|
24
|
+
when "blacklist"
|
25
|
+
blacklist = COMPONENTS.restful_rpc["blacklist"]
|
26
|
+
! blacklist.find do |pattern|
|
27
|
+
pattern_octets = pattern.split "."
|
28
|
+
# Traverse both arrays in parallel
|
29
|
+
octets.zip(pattern_octets).map do |octet, octet_pattern|
|
30
|
+
octet_pattern == "*" ? true : (octet == octet_pattern)
|
31
|
+
end == [true, true, true, true]
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise Adhearsion::Components::ConfigurationError, 'Unrecognized "access" configuration value!'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
RESTFUL_API_HANDLER = lambda do |env|
|
39
|
+
json = env["rack.input"].read
|
40
|
+
|
41
|
+
# Return "Bad Request" HTTP error if the client forgot
|
42
|
+
return [400, {}, "You must POST a valid JSON object!"] if json.blank?
|
43
|
+
|
44
|
+
json = JSON.parse json
|
45
|
+
|
46
|
+
nesting = COMPONENTS.restful_rpc["path_nesting"]
|
47
|
+
path = env["PATH_INFO"]
|
48
|
+
|
49
|
+
return [404, {}, "This resource does not respond to #{path.inspect}"] unless path[0...nesting.size] == nesting
|
50
|
+
|
51
|
+
path = path[nesting.size..-1]
|
52
|
+
|
53
|
+
return [404, {"Content-Type" => "application/json"}, "You cannot nest method names!"] if path.include?("/")
|
54
|
+
|
55
|
+
rpc_object = Adhearsion::Components.component_manager.extend_object_with(Object.new, :rpc)
|
56
|
+
|
57
|
+
# TODO: set the content-type and other HTTP headers
|
58
|
+
response_object = Array rpc_object.send(path, *json)
|
59
|
+
[200, {"Content-Type" => "application/json"}, response_object.to_json]
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
initialization do
|
64
|
+
config = COMPONENTS.restful_rpc
|
65
|
+
|
66
|
+
api = RESTFUL_API_HANDLER
|
67
|
+
|
68
|
+
port = config["port"] || 5000
|
69
|
+
authentication = config["authentication"]
|
70
|
+
show_exceptions = config["show_exceptions"]
|
71
|
+
handler = Rack::Handler.const_get(config["handler"] || "Mongrel")
|
72
|
+
|
73
|
+
if authentication
|
74
|
+
api = Rack::Auth::Basic.new(api) do |username, password|
|
75
|
+
authentication[username] == password
|
76
|
+
end
|
77
|
+
api.realm = "Adhearsion API"
|
78
|
+
end
|
79
|
+
|
80
|
+
if show_exceptions
|
81
|
+
api = Rack::ShowStatus.new(Rack::ShowExceptions.new(api))
|
82
|
+
end
|
83
|
+
|
84
|
+
Thread.new do
|
85
|
+
handler.run api, :Port => port
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Use path_nesting to specify an arbitrarily nested. Could be used for additional security or in HTTP reverse proxy server.
|
2
|
+
path_nesting: /
|
3
|
+
|
4
|
+
port: 5000
|
5
|
+
|
6
|
+
# The "handler" option here can be any valid Rack::Handler constant name.
|
7
|
+
# Other options: WEBrick, EventedMongrel.
|
8
|
+
# If you don't know the differences between these, "Mongrel" is definitely a good choice.
|
9
|
+
handler: Mongrel
|
10
|
+
|
11
|
+
# In a production system, you should make this "false" since
|
12
|
+
show_exceptions: true
|
13
|
+
|
14
|
+
# The "authentication" config option can either be "false" or key/value pairs representing allowed usernames and passwords.
|
15
|
+
|
16
|
+
#authentication: false
|
17
|
+
authentication:
|
18
|
+
jicksta: roflcopterz
|
19
|
+
foo: bar6213671682
|
20
|
+
|
21
|
+
access: everyone # When allowing "everyone" access, no IPs are blocked.
|
22
|
+
#access: whitelist # When using a whitelist, the "whitelist" data below will be used.
|
23
|
+
#access: blacklist # When using a blacklist, the "blacklist" data below will be used.
|
24
|
+
|
25
|
+
# This is a list of IPs which are exclusively allowed to call this web service.
|
26
|
+
# Note: whitelists are far more secure than blacklists.
|
27
|
+
whitelist:
|
28
|
+
- 127.0.0.1
|
29
|
+
- 192.168.*.*
|
30
|
+
|
31
|
+
# This is a list of the IPs which are explicitly NOT allowed to call this web service. This will only be used if "access" is
|
32
|
+
# set to "blacklist" above.
|
33
|
+
blacklist:
|
34
|
+
- 100.200.100.200
|
@@ -0,0 +1,263 @@
|
|
1
|
+
unless defined? Adhearsion
|
2
|
+
if File.exists? File.dirname(__FILE__) + "/../../../adhearsion/lib/adhearsion.rb"
|
3
|
+
# If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
|
4
|
+
# into a folder named "adhearsion" within this app's main directory.
|
5
|
+
require File.dirname(__FILE__) + "/../../../adhearsion/lib/adhearsion.rb"
|
6
|
+
elsif File.exists? File.dirname(__FILE__) + "/../../../../../../lib/adhearsion.rb"
|
7
|
+
# This file may be ran from the within the Adhearsion framework code (before a project has been generated)
|
8
|
+
require File.dirname(__FILE__) + "/../../../../../../lib/adhearsion.rb"
|
9
|
+
else
|
10
|
+
require 'rubygems'
|
11
|
+
gem 'adhearsion', '>= 0.7.999'
|
12
|
+
require 'adhearsion'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'adhearsion/component_manager/spec_framework'
|
17
|
+
|
18
|
+
RESTFUL_RPC = ComponentTester.new("restful_rpc", File.dirname(__FILE__) + "/../..")
|
19
|
+
|
20
|
+
##### This is here for a reference
|
21
|
+
#{"CONTENT_LENGTH" => "12",
|
22
|
+
# "CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
23
|
+
# "GATEWAY_INTERFACE" => "CGI/1.1",
|
24
|
+
# "HTTP_ACCEPT" => "application/xml",
|
25
|
+
# "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
26
|
+
# "HTTP_AUTHORIZATION" => "Basic amlja3N0YTpyb2ZsY29wdGVyeg==",
|
27
|
+
# "HTTP_HOST" => "localhost:5000",
|
28
|
+
# "HTTP_VERSION" => "HTTP/1.1",
|
29
|
+
# "PATH_INFO" => "/rofl",
|
30
|
+
# "QUERY_STRING" => "",
|
31
|
+
# "rack.errors" => StringIO.new(""),
|
32
|
+
# "rack.input" => StringIO.new('["o","hai!"]'),
|
33
|
+
# "rack.multiprocess" => false,
|
34
|
+
# "rack.multithread" => true,
|
35
|
+
# "rack.run_once" => false,
|
36
|
+
# "rack.url_scheme" => "http",
|
37
|
+
# "rack.version" => [0, 1],
|
38
|
+
# "REMOTE_ADDR" => "::1",
|
39
|
+
# "REMOTE_HOST" => "localhost",
|
40
|
+
# "REMOTE_USER" => "jicksta",
|
41
|
+
# "REQUEST_METHOD" => "POST"
|
42
|
+
# "REQUEST_PATH" => "/",
|
43
|
+
# "REQUEST_URI" => "http://localhost:5000/rofl",
|
44
|
+
# "SCRIPT_NAME" => "",
|
45
|
+
# "SERVER_NAME" => "localhost",
|
46
|
+
# "SERVER_PORT" => "5000",
|
47
|
+
# "SERVER_PROTOCOL" => "HTTP/1.1",
|
48
|
+
# "SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.8.6/2008-03-03)"}
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
describe "The VALID_IP_ADDRESS regular expression" do
|
53
|
+
|
54
|
+
it "should match only valid IP addresses" do
|
55
|
+
valid_ip_addresses = ["192.168.1.98", "10.0.1.200", "255.255.255.0", "123.*.4.*"]
|
56
|
+
invalid_ip_addresses = ["10.0.1.1 foo", "bar 255.255.255.0", "0*0*0*0", "1234"]
|
57
|
+
|
58
|
+
valid_ip_addresses. each { |ip| RESTFUL_RPC::VALID_IP_ADDRESS.should =~ ip }
|
59
|
+
invalid_ip_addresses.each { |ip| RESTFUL_RPC::VALID_IP_ADDRESS.should_not =~ ip }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "The initialization block" do
|
64
|
+
|
65
|
+
it "should create a new Thread" do
|
66
|
+
mock_component_config_with :restful_rpc => {}
|
67
|
+
mock(Thread).new { nil }
|
68
|
+
RESTFUL_RPC.initialize!
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should run the Rack adapter specified in the configuration" do
|
72
|
+
mock(Thread).new.yields
|
73
|
+
mock_component_config_with :restful_rpc => {"adapter" => "Mongrel"}
|
74
|
+
mock(Rack::Handler::Mongrel).run is_a(Proc), :Port => 5000
|
75
|
+
RESTFUL_RPC.initialize!
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should wrap the RESTFUL_API_HANDLER in an Rack::Auth::Basic object if authentication is enabled" do
|
79
|
+
mock(Thread).new.yields
|
80
|
+
mock_component_config_with :restful_rpc => {"authentication" => {"foo" => "bar"}}
|
81
|
+
|
82
|
+
proper_authenticator = lambda do |obj|
|
83
|
+
request = OpenStruct.new :credentials => ["foo", "bar"]
|
84
|
+
obj.is_a?(Rack::Auth::Basic) && obj.send(:valid?, request)
|
85
|
+
end
|
86
|
+
|
87
|
+
mock(Rack::Handler::Mongrel).run(satisfy(&proper_authenticator), :Port => 5000)
|
88
|
+
RESTFUL_RPC.initialize!
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should wrap the RESTFUL_API_HANDLER in ShowStatus and ShowExceptions objects when show_exceptions is enabled' do
|
92
|
+
mock(Thread).new.yields
|
93
|
+
mock_component_config_with :restful_rpc => {"show_exceptions" => true}
|
94
|
+
|
95
|
+
mock.proxy(Rack::ShowExceptions).new(is_a(Proc))
|
96
|
+
mock.proxy(Rack::ShowStatus).new is_a(Rack::ShowExceptions)
|
97
|
+
|
98
|
+
mock(Rack::Handler::Mongrel).run is_a(Rack::ShowStatus), :Port => 5000
|
99
|
+
RESTFUL_RPC.initialize!
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'Private helper methods' do
|
105
|
+
|
106
|
+
describe "the RESTFUL_API_HANDLER lambda" do
|
107
|
+
|
108
|
+
it "should return a 200 for requests which execute a method that has been defined in the methods_for(:rpc) context" do
|
109
|
+
component_manager = Adhearsion::Components::ComponentManager.new('/path/shouldnt/matter')
|
110
|
+
|
111
|
+
mock(Adhearsion::Components).component_manager { component_manager }
|
112
|
+
component_manager.load_code <<-RUBY
|
113
|
+
methods_for(:rpc) do
|
114
|
+
def testing_123456(one,two)
|
115
|
+
[two.reverse, one.reverse]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
RUBY
|
119
|
+
|
120
|
+
input = StringIO.new %w[jay phillips].to_json
|
121
|
+
|
122
|
+
mock_component_config_with :restful_rpc => {"path_nesting" => "/"}
|
123
|
+
|
124
|
+
env = {"PATH_INFO" => "/testing_123456", "rack.input" => input}
|
125
|
+
|
126
|
+
response = RESTFUL_RPC::RESTFUL_API_HANDLER.call(env)
|
127
|
+
response.should be_kind_of(Array)
|
128
|
+
response.should have(3).items
|
129
|
+
response.first.should equal(200)
|
130
|
+
JSON.parse(response.last).should eql(%w[jay phillips].map(&:reverse).reverse)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return a 400 when no data is POSTed" do
|
134
|
+
env = {"rack.input" => StringIO.new(""), "REQUEST_URI" => "/foobar"}
|
135
|
+
RESTFUL_RPC::RESTFUL_API_HANDLER.call(env).first.should equal(400)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should work with a high level test of a successful method invocation" do
|
139
|
+
|
140
|
+
component_manager = Adhearsion::Components::ComponentManager.new('/path/shouldnt/matter')
|
141
|
+
|
142
|
+
mock(Adhearsion::Components).component_manager { component_manager }
|
143
|
+
|
144
|
+
component_manager.load_code '
|
145
|
+
methods_for(:rpc) do
|
146
|
+
def rofl(one,two)
|
147
|
+
"Hai! #{one} #{two}"
|
148
|
+
end
|
149
|
+
end'
|
150
|
+
|
151
|
+
env = {
|
152
|
+
"CONTENT_LENGTH" => "12",
|
153
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
154
|
+
"GATEWAY_INTERFACE" => "CGI/1.1",
|
155
|
+
"HTTP_ACCEPT" => "application/xml",
|
156
|
+
"HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
157
|
+
"HTTP_AUTHORIZATION" => "Basic amlja3N0YTpyb2ZsY29wdGVyeg==",
|
158
|
+
"HTTP_HOST" => "localhost:5000",
|
159
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
160
|
+
"PATH_INFO" => "/rofl",
|
161
|
+
"QUERY_STRING" => "",
|
162
|
+
"rack.errors" => StringIO.new(""),
|
163
|
+
"rack.input" => StringIO.new('["o","hai!"]'),
|
164
|
+
"rack.multiprocess" => false,
|
165
|
+
"rack.multithread" => true,
|
166
|
+
"rack.run_once" => false,
|
167
|
+
"rack.url_scheme" => "http",
|
168
|
+
"rack.version" => [0, 1],
|
169
|
+
"REMOTE_ADDR" => "::1",
|
170
|
+
"REMOTE_HOST" => "localhost",
|
171
|
+
"REMOTE_USER" => "jicksta",
|
172
|
+
"REQUEST_METHOD" => "POST",
|
173
|
+
"REQUEST_PATH" => "/",
|
174
|
+
"REQUEST_URI" => "http://localhost:5000/rofl",
|
175
|
+
"SCRIPT_NAME" => "",
|
176
|
+
"SERVER_NAME" => "localhost",
|
177
|
+
"SERVER_PORT" => "5000",
|
178
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
179
|
+
"SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.8.6/2008-03-03)" }
|
180
|
+
|
181
|
+
response = RESTFUL_RPC::RESTFUL_API_HANDLER.call(env)
|
182
|
+
JSON.parse(response.last).should == ["Hai! o hai!"]
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should contain backtrace information when show_errors is enabled and an exception occurs" do
|
187
|
+
mock_component_config_with :restful_api => {"show_errors" => true}
|
188
|
+
pending
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'the ip_allowed?() method' do
|
194
|
+
|
195
|
+
before :each do
|
196
|
+
@method = RESTFUL_RPC.helper_method :ip_allowed?
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should raise a ConfigurationError if "access" is not one of "everyone", "whitelist" or "blacklist"' do
|
200
|
+
good_access_values = %w[everyone whitelist blacklist]
|
201
|
+
bad_access_values = %w[foo bar qaz qwerty everone blaclist whitlist]
|
202
|
+
|
203
|
+
good_access_values.each do |access_value|
|
204
|
+
mock_component_config_with :restful_rpc => {"access" => access_value, "whitelist" => [], "blacklist" => []}
|
205
|
+
lambda { @method.call("10.0.0.1") }.should_not raise_error
|
206
|
+
end
|
207
|
+
|
208
|
+
bad_access_values.each do |access_value|
|
209
|
+
mock_component_config_with :restful_rpc => {"access" => access_value, "authentication" => false}
|
210
|
+
lambda { @method.call("10.0.0.1") }.should raise_error(Adhearsion::Components::ConfigurationError)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'whitelists' do
|
215
|
+
|
216
|
+
it "should parse *'s as wildcards" do
|
217
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => ["10.*.*.*"]}
|
218
|
+
@method.call("10.1.2.3").should be_true
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should allow IPs which are explictly specified" do
|
222
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => ["4.3.2.1"]}
|
223
|
+
@method.call("4.3.2.1").should be_true
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should not allow IPs which are not explicitly specified" do
|
227
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => %w[ 1.2.3.4 4.3.2.1]}
|
228
|
+
@method.call("2.2.2.2").should be_false
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'blacklists' do
|
234
|
+
|
235
|
+
it "should parse *'s as wildcards" do
|
236
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["10.*.*.*"]}
|
237
|
+
@method.call("10.1.2.3").should be_false
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should not allow IPs which are explicitly specified" do
|
241
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["9.8.7.6", "9.8.7.5"]}
|
242
|
+
@method.call("9.8.7.5").should be_false
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should allow IPs which are not explicitly specified" do
|
246
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["10.20.30.40"]}
|
247
|
+
@method.call("1.1.1.1").should be_true
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '"everyone" access' do
|
253
|
+
it "should return true for any IP given, irrespective of the configuration" do
|
254
|
+
ip_addresses = %w[100.200.100.200 0.0.0.0 *.0.0.*]
|
255
|
+
ip_addresses.each do |address|
|
256
|
+
RESTFUL_RPC.helper_method(:ip_allowed?).call(address).should equal(true)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'md5'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
SANDBOX_VERSION = 1.0
|
5
|
+
|
6
|
+
initialization do
|
7
|
+
# We shouldn't start initializing until after the AGI server has initialized.
|
8
|
+
Events.register_callback(:after_initialized) do
|
9
|
+
ahn_log.sandbox "Fetching sandbox connection information"
|
10
|
+
|
11
|
+
config = if COMPONENTS.sandbox.has_key? "connect_to"
|
12
|
+
{"connect_to" => COMPONENTS.sandbox["connect_to"]}
|
13
|
+
else
|
14
|
+
begin
|
15
|
+
yaml_data = open("http://sandbox.adhearsion.com/component/#{SANDBOX_VERSION}").read
|
16
|
+
YAML.load yaml_data
|
17
|
+
rescue SocketError
|
18
|
+
ahn_log.sandbox.error "Could not connect to the sandbox server! Skipping sandbox initialization!"
|
19
|
+
next
|
20
|
+
rescue => e
|
21
|
+
ahn_log.sandbox.error "COULD NOT RETRIEVE SANDBOX CONNECTION INFORMATION! Not initializing sandbox component!"
|
22
|
+
next
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
# The "connect_to" key is what this version supports
|
28
|
+
if config.kind_of?(Hash) && config.has_key?("connect_to")
|
29
|
+
config = config['connect_to']
|
30
|
+
|
31
|
+
host, port = config.values_at "host", "port"
|
32
|
+
|
33
|
+
ahn_log.sandbox "Connecting to #{host}:#{port}"
|
34
|
+
|
35
|
+
username, password = COMPONENTS.sandbox["username"], COMPONENTS.sandbox["password"]
|
36
|
+
|
37
|
+
if username.blank? || password.blank? || username == "user123"
|
38
|
+
ahn_log.sandbox.error "You must specify your username and password in this component's config file!"
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
# Part of the AGI-superset protocol we use to log in.
|
43
|
+
identifying_hash = MD5.md5(username + ":" + password).to_s
|
44
|
+
|
45
|
+
if host.nil? || port.nil?
|
46
|
+
ahn_log.sandbox.error "Invalid YAML returned from server! Skipping sandbox initialization!"
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
Thread.new do
|
51
|
+
loop do
|
52
|
+
begin
|
53
|
+
ahn_log.sandbox.debug "Establishing outbound AGI socket"
|
54
|
+
socket = TCPSocket.new(host, port)
|
55
|
+
socket.puts identifying_hash
|
56
|
+
response = socket.gets
|
57
|
+
unless response
|
58
|
+
ahn_log.sandbox "Communication with the sandbox ended before receiving any response. Reconnecting."
|
59
|
+
next
|
60
|
+
end
|
61
|
+
response.chomp!
|
62
|
+
case
|
63
|
+
when "authentication accepted"
|
64
|
+
ahn_log.sandbox "Authentication accepted"
|
65
|
+
|
66
|
+
start_signal = socket.gets
|
67
|
+
next unless start_signal
|
68
|
+
start_signal.chomp!
|
69
|
+
|
70
|
+
if start_signal
|
71
|
+
ahn_log.sandbox "Incoming call from remote sandbox server!"
|
72
|
+
begin
|
73
|
+
Adhearsion::Initializer::AsteriskInitializer.agi_server.server.serve(socket)
|
74
|
+
rescue => e
|
75
|
+
ahn_log.error "Non-fatal exception in the AGI server: #{e.inspect} \n" + e.backtrace.join("\n")
|
76
|
+
ensure
|
77
|
+
socket.close
|
78
|
+
end
|
79
|
+
ahn_log.sandbox "AGI server finished serving call. Reconnecting to sandbox."
|
80
|
+
else
|
81
|
+
ahn_log.sandbox "Remote Asterisk server received no call. Reconnecting..."
|
82
|
+
end
|
83
|
+
when "authentication failed"
|
84
|
+
ahn_log.sandbox.error "Your username or password is invalid! Skipping sandbox initialization..."
|
85
|
+
break
|
86
|
+
when /^wait (\d+)$/
|
87
|
+
sleep response[/^wait (\d+)$/,1].to_i
|
88
|
+
else
|
89
|
+
ahn_log.sandbox.error "Invalid login acknowledgement! Skipping sandbox initialization!"
|
90
|
+
break
|
91
|
+
end
|
92
|
+
rescue Errno::ECONNREFUSED
|
93
|
+
ahn_log.sandbox.error "Could not connect to the sandbox server! Sandbox component stopping..."
|
94
|
+
break
|
95
|
+
rescue => e
|
96
|
+
ahn_log.error "Unrecognized error: #{e.inspect} \n" + e.backtrace.join("\n")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
else
|
102
|
+
ahn_log.sandbox.error "COULD NOT RETRIEVE SANDBOX CONNECTION INFORMATION! Not initializing sandbox component!"
|
103
|
+
end
|
104
|
+
rescue => e
|
105
|
+
ahn_log.sandbox.error "Encountered an error when connecting to the sandbox! #{e.message}\n" + e.backtrace.join("\n")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -35,7 +35,7 @@ Setting up a Ruby Stomp server
|
|
35
35
|
|
36
36
|
Install the pure-Ruby Stomp server by doing "gem install stompserver". This will add the "stompserver" command to your system. When running it without any parameters, it starts without requiring authentication. If you're wanting to get a quick experiment running, I recommend simply doing that.
|
37
37
|
|
38
|
-
Open the
|
38
|
+
Open the `stomp_gateway.yml` file in the stomp_gateway component folder. Comment out the four settings at the top of the file named "user", "pass", "host" and "port" by prepending a "#" to their line. This will cause the component to choose defaults for those properties. The component's defaults will match the expected credentials for the experimental stompserver you're already running on your computer.
|
39
39
|
|
40
40
|
You also need specify a subscription name in
|
41
41
|
|
data/lib/adhearsion/cli.rb
CHANGED
@@ -115,7 +115,7 @@ USAGE
|
|
115
115
|
# look at the examples in newly-created projects.
|
116
116
|
RUBY
|
117
117
|
end
|
118
|
-
File.open(new_component_dir + "
|
118
|
+
File.open(new_component_dir + "/#{component_name}.yml","w") do |file|
|
119
119
|
file.puts '# You can use this file for component-specific configuration.'
|
120
120
|
end
|
121
121
|
puts "Created blank component '#{component_name}' at components/#{component_name}"
|
@@ -18,7 +18,6 @@ module Adhearsion
|
|
18
18
|
end
|
19
19
|
|
20
20
|
SCOPE_NAMES = [:dialplan, :events, :generators, :rpc, :global]
|
21
|
-
DEFAULT_CONFIG_NAME = "config.yml"
|
22
21
|
|
23
22
|
attr_reader :scopes, :lazy_config_loader
|
24
23
|
def initialize(path_to_container_directory)
|
@@ -61,7 +60,7 @@ module Adhearsion
|
|
61
60
|
#
|
62
61
|
def configuration_for_component_named(component_name)
|
63
62
|
component_dir = File.join(@path_to_container_directory, component_name)
|
64
|
-
config_file = File.join component_dir,
|
63
|
+
config_file = File.join component_dir, "#{component_name}.yml"
|
65
64
|
if File.exists?(config_file)
|
66
65
|
YAML.load_file config_file
|
67
66
|
else
|
@@ -11,7 +11,8 @@ module Adhearsion
|
|
11
11
|
require_dependencies
|
12
12
|
require_models
|
13
13
|
@@config = Adhearsion::AHN_CONFIG.database
|
14
|
-
|
14
|
+
# You may need to uncomment the following line for older versions of ActiveRecord
|
15
|
+
# ActiveRecord::Base.allow_concurrency = true
|
15
16
|
establish_connection
|
16
17
|
create_call_hook_for_connection_cleanup
|
17
18
|
end
|
@@ -17,7 +17,8 @@ module Adhearsion
|
|
17
17
|
"It's not a directory!" unless File.directory?(rails_root)
|
18
18
|
load_rails
|
19
19
|
if defined? ActiveRecord
|
20
|
-
|
20
|
+
# You may need to uncomment the following line for older versions of ActiveRecord
|
21
|
+
# ActiveRecord::Base.allow_concurrency = true
|
21
22
|
Events.register_callback([:asterisk, :before_call]) do
|
22
23
|
ActiveRecord::Base.verify_active_connections!
|
23
24
|
end
|
data/lib/adhearsion/version.rb
CHANGED
@@ -34,8 +34,8 @@ module Adhearsion
|
|
34
34
|
end
|
35
35
|
rescue HungupExtensionCallException => hungup_call
|
36
36
|
begin
|
37
|
-
ahn_log.agi "Received \"h\" meta-call. Executing /asterisk/
|
38
|
-
Events.trigger [:asterisk, :
|
37
|
+
ahn_log.agi "Received \"h\" meta-call. Executing /asterisk/hungup_call event callbacks."
|
38
|
+
Events.trigger [:asterisk, :hungup_call], hungup_call.call
|
39
39
|
call.hangup!
|
40
40
|
rescue => e
|
41
41
|
ahn_log.agi.error e
|
@@ -78,4 +78,4 @@ module Adhearsion
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
|
-
end
|
81
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jicksta-adhearsion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jay Phillips
|
@@ -46,14 +46,16 @@ files:
|
|
46
46
|
- app_generators/ahn/templates/.ahnrc
|
47
47
|
- app_generators/ahn/templates/components/ami_remote/ami_remote.rb
|
48
48
|
- app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE
|
49
|
-
- app_generators/ahn/templates/components/disabled/stomp_gateway/
|
49
|
+
- app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.yml
|
50
50
|
- app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown
|
51
51
|
- app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb
|
52
|
-
- app_generators/ahn/templates/components/
|
53
|
-
- app_generators/ahn/templates/components/
|
54
|
-
- app_generators/ahn/templates/components/restful_rpc/
|
55
|
-
- app_generators/ahn/templates/components/restful_rpc/
|
56
|
-
- app_generators/ahn/templates/components/restful_rpc/
|
52
|
+
- app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb
|
53
|
+
- app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml
|
54
|
+
- app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml
|
55
|
+
- app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb
|
56
|
+
- app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown
|
57
|
+
- app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb
|
58
|
+
- app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb
|
57
59
|
- app_generators/ahn/templates/components/simon_game/simon_game.rb
|
58
60
|
- app_generators/ahn/templates/config/startup.rb
|
59
61
|
- app_generators/ahn/templates/dialplan.rb
|
@@ -148,7 +150,6 @@ files:
|
|
148
150
|
- lib/theatre/version.rb
|
149
151
|
- LICENSE
|
150
152
|
- Rakefile
|
151
|
-
- README.txt
|
152
153
|
has_rdoc: false
|
153
154
|
homepage: http://adhearsion.com
|
154
155
|
post_install_message:
|
data/README.txt
DELETED
@@ -1,5 +0,0 @@
|
|
1
|
-
Adhearsion
|
2
|
-
by Jay Phillips, et. al
|
3
|
-
http://adhearsion.com
|
4
|
-
|
5
|
-
Adhearsion is an open-source framework written in Ruby designed to vastly improve Voice over IP development. It rests atop established telephony platforms (such as Asterisk and Freeswitch) and abstracts their complexity with the beauty of a well-designed, high-level suite of solutions.
|