developergarden_sdk 0.0.8

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/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ == developergarden_sdk_handsoap
2
+
3
+ Put appropriate LICENSE for your project here.
data/README ADDED
@@ -0,0 +1,177 @@
1
+ == developergarden_sdk
2
+ This library provides access to open development services of the Deutsche Telekom AG.
3
+ For more details about the services see
4
+ http://www.developergarden.com
5
+
6
+ Depending on your operating system you might want to skip the "sudo" command prior to the
7
+ commands listed here.
8
+
9
+ === Dependencies
10
+ In order to use this library the following dependencies need to be met:
11
+ Ruby 1.8.6
12
+ Ruby Gems >= 1.3.1
13
+
14
+ There might be issues with Ruby 1.8.7 regarding to curb so it is highly recommended to use Ruby 1.8.6.
15
+
16
+ ==== Gems
17
+ The following gems need to be installed:
18
+ * handsoap (Version 0.2.8)
19
+ * nokogiri
20
+ * httpclient (Version 2.1.14)
21
+ * htmlentities
22
+
23
+ === Install the handsoap gem
24
+ For production use the official release of handsoap should be installed.
25
+ More information about handsoap can be found at:
26
+ http://github.com/troelskn/handsoap/tree/master
27
+
28
+ ==== Install official handsoap release
29
+ gem sources -a http://gems.github.com
30
+ sudo gem install troelskn-handsoap
31
+
32
+ ==== Install handsoap developer version
33
+ If you want to use the bleeding edge version of handsoap proceed as follows.
34
+ Note that you do not need to perform these steps if you have already installed the official release!
35
+
36
+ Checkout handsoap from github
37
+ git clone git://github.com/troelskn/handsoap.git
38
+
39
+ Install jeweler
40
+ sudo gem install jeweler
41
+
42
+ Build handsoap package
43
+ cd handsoap_git_repo
44
+ rake build
45
+
46
+ Install the resulting gem file
47
+ cd pkg
48
+ sudo gem install handsoap-0.2.7.gem
49
+
50
+ Attention: if you you use the development version you might need to change the gem statement in
51
+ basic_service.rb from
52
+
53
+ gem 'troelskn-handsoap'
54
+
55
+ to
56
+
57
+ gem 'handsoap'
58
+
59
+ === Installation of the developergarden_sdk Gem
60
+ Install the gem using the following command
61
+ gem install developergarden_sdk-0.0.x.gem
62
+
63
+ Where x stands for the current version. For the version 0.0.4 this could look like this:
64
+ gem install developergarden_sdk-0.0.x.gem
65
+
66
+ === Basic usage
67
+ You can use the gem from pure Ruby applications and of course from Ruby on Rails apps as well.
68
+
69
+ ==== To use the gem from your Ruby app:
70
+
71
+ require 'rubygems'
72
+ gem 'developergarden_sdk'
73
+ require 'token_Service/token_service'
74
+ require 'sms_service/sms_service'
75
+ require 'voice_call_service/voice_call_service'
76
+ require 'quota_service/quota_service'
77
+ require 'service_environment'
78
+
79
+ ==== Ruby Example to send a sms
80
+ #!/usr/bin/env ruby -d
81
+
82
+ require 'rubygems'
83
+ gem 'developergarden_sdk'
84
+ require 'token_Service/token_service'
85
+ require 'sms_service/sms_service'
86
+ require 'service_environment'
87
+
88
+ sms = SmsService::SmsService.new("<USER>@t-online.de", "<PASSWORD>")
89
+ sms_response = sms.send_sms("+49177 0000001", "Your message text.", "RubySDK", ServiceEnvironment.PRODUCTION, "")
90
+
91
+
92
+ ==== To use your gem from your Ruby on Rails app
93
+ In your environment.rb add the following line in the config block:
94
+ config.gem 'developergarden_sdk'
95
+
96
+ ==== Ruby on Rails Example
97
+
98
+ class SmsNotification
99
+
100
+ USERNAME = "<USER>@t-online.de"
101
+ PASSWORD = "<PASSWORD>"
102
+
103
+ # Send SMS to Numbers.
104
+ def send_sms(number, message, originator, environment = 2)
105
+
106
+ # Create new SmsService instance
107
+ @sms = SmsService::SmsService.new(USERNAME, PASSWORD)
108
+
109
+ # Send SMS to cell phone
110
+ @sms.send_sms(number, message, originator, environment)
111
+
112
+ end
113
+
114
+ end
115
+
116
+ === More Examples
117
+ In order to see more examples have a look the unit tests included in the gem's source code.
118
+ You might also want to have a look at the actual source code and source code comments.
119
+
120
+ === Debug
121
+ Per default all generated and received soap xml messages are not visible. These can be print to stdout for debugging
122
+ purposes by starting the ruby interpreter with the -d option. You will then see all http xml soap requests and responses.
123
+ This can be done by invoking your ruby app by using a shebang like this:
124
+ #!/usr/bin/ruby -d
125
+
126
+ === Testing
127
+ Before you run the tests you will need to modify config/test_settings.yml by entering your developer garden credentials.
128
+
129
+ Executing tests will be done in the "sandbox" or "mock" environment which are free of charge.
130
+ Be ware that the services itself have quotas on these test environments so your tests may fail after a while.
131
+ The quotas are on a daily basis so they should pass again the next day.
132
+
133
+ Failures when reaching the SANDBOX QUOTA might look like this:
134
+ 0030 quotas have exceeded
135
+
136
+ Phone numbers in test files are fictive and for testing purposes only.
137
+
138
+ The testsuite can be run by invoking the test raketask.
139
+ cd /home/yourhome/developergarden_sdk
140
+ rake test
141
+
142
+ In order to run a single test perform the following command:
143
+ cd /home/yourhome/developergarden_sdk/lib
144
+ ruby -d ../test/voice_call_service_test.rb --name teardown_call
145
+
146
+ ==== QuotaService
147
+ * QuotaService can be tested as it is. There is no need to pass an environment such as "production" or "sandbox" because
148
+ it is free of charge.
149
+
150
+ ==== SmsService
151
+ * SmsService can be tested using the "sandbox" environment. Be aware that there is a 5 credit quota per day. So you can
152
+ send 5 test sms per day. After that you will receive a corresponding quota error message.
153
+
154
+ ==== VoiceCallService
155
+ * In the "sandbox" environment there is a quota limit of 5 calls per day (each up to 10 sec) for the VoiceCallService
156
+ * In the "production" environment a call may not last more than 60 minutes. After 60 min the call will be cancelled.
157
+ * There is also a quota for the "mock" environment.
158
+
159
+ For more information about service environments have a look at the documentation at http://www.developergarden.com
160
+
161
+ === Build the developergarden_sdk gem
162
+ You can build the gem by invoking the gem rake task
163
+ rake gem
164
+ The resulting gem will be generated to the pkg/ directory and can be installed like this:
165
+ sudo gem install developergarden_sdk-0.0.6.gem
166
+ Depending on the current version number you will need to adapt the gem filename correspondingly.
167
+
168
+ ==More Information
169
+ More information about developer garden services can be found at:
170
+ http://www.developergarden.com
171
+
172
+ ==FAQ
173
+ === Did not understand "MustUnderstand" header(s)
174
+ Handsoap::Fault: Handsoap::Fault { :code => 'soapenv:MustUnderstand', :reason => 'Did not understand "MustUnderstand" header(s):{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security' }
175
+
176
+ You will receive this error message if a mandatory header element is not present or the remote service was unable to process it.
177
+ Most likely this will happen if something is wrong during the authentication process such as missing parameters like username, password.
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ #
2
+ # To change this template, choose Tools | Templates
3
+ # and open the template in the editor.
4
+
5
+
6
+ require 'rubygems'
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'rake/gempackagetask'
10
+ require 'rake/rdoctask'
11
+ require 'rake/testtask'
12
+ require 'fileutils'
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = 'developergarden_sdk'
16
+ s.version = '0.0.8'
17
+ s.homepage = 'http://www.developergarden.com'
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ['README', 'LICENSE']
20
+ s.summary = 'Offers a ruby client for the open development services such as send SMS, voice call and quota management of the Deutsche Telekom AG. See also http://www.developergarden.com.'
21
+ s.description = s.summary
22
+ s.author = 'Julian Fischer / Aperto move GmbH'
23
+ s.email = 'ruby@developergarden.com'
24
+ # s.executables = ['your_executable_here']
25
+
26
+ # GEM dependencies
27
+ s.add_dependency 'httpclient', '= 2.1.4'
28
+ s.add_dependency 'nokogiri', '>= 1.3.1'
29
+ s.add_dependency 'troelskn-handsoap', '= 0.2.8'
30
+ s.add_dependency 'htmlentities', '>= 4.0.0'
31
+
32
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
33
+ s.require_path = "lib"
34
+ s.bindir = "bin"
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |p|
38
+ p.gem_spec = spec
39
+ p.need_tar = true
40
+ p.need_zip = true
41
+ end
42
+
43
+ Rake::RDocTask.new do |rdoc|
44
+ files =['README', 'LICENSE', 'lib/**/*.rb']
45
+ rdoc.rdoc_files.add(files)
46
+ rdoc.main = "README" # page to start on
47
+ rdoc.title = "developergarden_sdk Docs"
48
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
49
+ rdoc.options << '--line-numbers'
50
+ rdoc.options << '--all'
51
+ rdoc.options << '--charset=UTF-8'
52
+ end
53
+
54
+ Rake::TestTask.new do |t|
55
+ t.test_files = FileList['test/**/*.rb']
56
+ end
57
+
58
+ desc "update_plugin"
59
+ task :update_plugin do
60
+ sh %{ cp -R lib/* plugin/developergarden_sdk/lib/ }
61
+ sh %{ cp -R test/* plugin/developergarden_sdk/test/ }
62
+ end
63
+
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/basic_service'
2
+ require File.dirname(__FILE__) + '/token_service/token_service'
3
+
4
+ # Base service for all services demanding a security token.
5
+ class AuthenticatedService < BasicService
6
+
7
+ def initialize(username, password)
8
+ super(username, password)
9
+
10
+ @token_service = TokenService::TokenService.new(@username, @password)
11
+ end
12
+
13
+ # Invokes the given action and also adds the security token to the SOAP header.
14
+ # Using this method authentication is totally hidden from the rest of the application.
15
+ def invoke_authenticated(action, &block)
16
+
17
+ security_token = @token_service.get_security_token
18
+
19
+ response = invoke(action) do |message|
20
+ doc = message.document
21
+
22
+ # Build the login header
23
+ build_service_header(doc, security_token)
24
+
25
+ yield(message, doc)
26
+ end
27
+
28
+ return response
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/service_exception'
2
+
3
+ # Base class for service responses.
4
+ class BasicResponse
5
+ attr_accessor :error_code, :error_message
6
+
7
+ # Constructor
8
+ def inizialize
9
+ end
10
+
11
+ # Constructor.
12
+ # ===Parameters
13
+ # <tt>response_xml</tt>:: Xml as returned by a <tt>status</tt>-method call.
14
+ # <tt>raise_exception_on_error</tt>:: Xml as returned by a <tt>status</tt>-method call.
15
+ def initialize(response_xml, raise_exception_on_error = true)
16
+ doc = response_xml.document
17
+
18
+ @error_code = doc.xpath("//errorCode").text
19
+ @error_message = doc.xpath("//errorMessage").text
20
+
21
+ raise_on_error(response_xml) if raise_exception_on_error
22
+ end
23
+
24
+ # Returns <tt>self.inspect</tt>. Good for debugging purposes.
25
+ def to_s
26
+ return self.inspect
27
+ end
28
+
29
+ # Raises an exception if the response is an error.
30
+ # Since in some response types the error code is not named "errorCode" it is not possible
31
+ # to call this method only in the constructur of this base class.
32
+ # This is sufficient for services using the BaseResponse directly but subclasses of BaseResponse
33
+ # need to call this method at the end of their constructure <tt>initialize</tt>.
34
+ def raise_on_error(response_xml)
35
+ if @error_code && !@error_code.empty? && @error_code != "0000" then
36
+
37
+ # It is important to create the exception with self.class.new and not only BasicResponse.new
38
+ # This is because this method can be also invoked from a subclass. If a VoiceCallResponse object for example
39
+ # raises an exception you will receive "VoiceCallResponse" from self.class. This is very important because
40
+ # only the VoiceCallResponse knows that in this case the response code is called <tt>status</tt>.
41
+ # This is because there is an inconsistency within the different service responses.
42
+ raise(ServiceException.new( self.class.new(response_xml, false) ), "The developer garden service you invoked responded with an error: " + @error_message.to_s)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,88 @@
1
+ require 'rubygems'
2
+ gem 'troelskn-handsoap'
3
+
4
+ require 'handsoap'
5
+ require File.dirname(__FILE__) + '/common/xml_tools'
6
+ require File.dirname(__FILE__) + '/service_environment'
7
+
8
+ # Implements basic logic used by developer garden ruby service implementations.
9
+ class BasicService < Handsoap::Service
10
+
11
+ @@SERVICE_ID = "https://odg.t-online.de"
12
+
13
+ # Create some namespaces
14
+ on_create_document do |doc|
15
+ doc.alias 'ns1', "http://sts.idm.telekom.com/schema/"
16
+ doc.alias 'xmlns:ns2', "Security"
17
+ end
18
+
19
+ # Constructor
20
+ # ===Parameters
21
+ # <tt>username</tt>:: Username, such as myuser@t-online.de
22
+ # <tt>password</tt>:: Password
23
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment
24
+ def initialize(username, password, environment = ServiceEnvironment.SANDBOX)
25
+ @username = username
26
+ @password = password
27
+ @environment = environment
28
+ end
29
+
30
+ protected
31
+
32
+ # After being successfully authenticated service calls need to performed including the security token(s) in its soap
33
+ # headers. This method builds such a header.
34
+ #
35
+ # The header of the given document will be enhanced so there is no need to
36
+ # process the returning value.
37
+ #
38
+ # ===Parameters
39
+ # <tt>doc</tt>:: Request XmlMason document.
40
+ # <tt>security_token</tt>:: Security tokens as plain text gathered using the TokenService.
41
+ def build_service_header(doc, security_token)
42
+ header = build_security_header_common(doc)
43
+ security = header.find("Security")
44
+ security.set_value( security_token, :raw)
45
+ return header
46
+ end
47
+
48
+ # Regardless to whether it is a <tt>login</tt>, <tt>getTokens</tt> or a regular service call there always
49
+ # has to be a security header. This method provides the structure which is in common to all these method calls.
50
+ #
51
+ # The header of the given document will be enhanced so there is no need to
52
+ # process the returning value.
53
+ # ===Parameters
54
+ # <tt>doc</tt>:: Request XmlMason document.
55
+ def build_security_header_common(doc)
56
+
57
+ # Get the header element
58
+ header = doc.find('Header')
59
+
60
+ # Add plain security element
61
+ header.add('Security')
62
+
63
+ # Find security element for further enhancement
64
+ security = header.find('Security')
65
+
66
+ # Set namespace
67
+ security.set_attr("xmlns", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")
68
+ security.set_attr("env:mustUnderstand", "1")
69
+ return header
70
+ end
71
+
72
+
73
+ # Extracts any token from the response and returns it.
74
+ # ===Parameters
75
+ # <tt>response</tt>:: Response as returned from a <tt>getTokens</tt> call, for example.
76
+ #
77
+ # ==Return
78
+ # Returns the security token as plain text to be inserted into a security header build with <tt>build_security_header_common</tt>.
79
+ def get_token_data_from_response(response)
80
+
81
+ # nokogiri document. Unfortunately it is unable to parse the returning xml completely.
82
+ # Especially the body is not parsed completely.
83
+ doc = response.document
84
+ intermediate_token = doc.xpath("//schema:tokenData", "schema" => 'http://sts.idm.telekom.com/schema/').inner_text
85
+
86
+ return intermediate_token
87
+ end
88
+ end
@@ -0,0 +1,25 @@
1
+ module Common
2
+
3
+ # Some tools facilitating xml processing.
4
+ class XmlTools
5
+
6
+ # Get rid of surrounding cdata sections.
7
+ # Attention: This method will modify the given string itself.
8
+ # ===Parameter
9
+ # <tt>xml_with_cdata</tt>:: Some xml string with a cdata section.
10
+ def self.strip_cdata!(xml_with_cdata)
11
+ xml_with_cdata.gsub!('<![CDATA[', '')
12
+ xml_with_cdata.gsub!(']]>', '')
13
+ return xml_with_cdata
14
+ end
15
+
16
+ # Get rid of surrounding cdata sections.
17
+ # ===Parameter
18
+ # <tt>xml_with_cdata</tt>:: Some xml string with a cdata section.
19
+ def self.strip_cdata(xml_with_cdata)
20
+ xml_with_cdata.gsub('<![CDATA[', '')
21
+ xml_with_cdata.gsub(']]>', '')
22
+ return xml_with_cdata
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/../basic_response'
2
+
3
+ module QuotaService
4
+
5
+ # Representing the response of a <tt>get_quota_information</tt>-Call.
6
+ class QuotaInformation < BasicResponse
7
+ attr_accessor :error_code, :error_message, :max_quota, :max_user_quota, :quota_level
8
+
9
+ # Constructor.
10
+ # ===Parameters
11
+ # <tt>response_xml</tt>:: Xml as returned by a <tt>call_status</tt>-method call.
12
+ # <tt>raise_exception_on_error</tt>:: Xml as returned by a <tt>call_status</tt>-method call.
13
+ def initialize(response_xml, raise_exception_on_error = true)
14
+ super(response_xml)
15
+
16
+ doc = response_xml.document
17
+ @max_quota = doc.xpath("//maxQuota").text
18
+ @max_user_quota = doc.xpath("//maxUserQuota").text
19
+ @quota_level = doc.xpath("//quotaLevel").text
20
+
21
+ raise_on_error(response_xml) if raise_exception_on_error
22
+ end
23
+
24
+ def max_quota
25
+ @max_quota.to_i
26
+ end
27
+
28
+ def max_user_quota
29
+ @max_user_quota.to_i
30
+ end
31
+
32
+ def quota_level
33
+ @quota_level.to_i
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/../authenticated_service'
2
+ require File.dirname(__FILE__) + '/../quota_service/quota_information'
3
+
4
+ Handsoap.http_driver = :httpclient
5
+
6
+ # Print http and soap requests and reponses if ruby has been started with -d option.
7
+ Handsoap::Service.logger = $stdout if $DEBUG
8
+
9
+ module QuotaService
10
+
11
+ # Client to access the developer garden quota service.
12
+ #
13
+ # See also:
14
+ # * https://www.developergarden.com/openapi/dokumentation/uebergreifende_informationen#2.5.
15
+ # * https://www.developergarden.com/openapi/dokumentation/services#4.2.6.
16
+ class QuotaService < AuthenticatedService
17
+
18
+ @@QUOTA_SERVICE_ENDPOINT = {
19
+ :uri => "https://gateway.developer.telekom.com/p3gw-mod-odg-admin/services/ODGBaseUserService",
20
+ :version => 1
21
+ }
22
+
23
+ endpoint @@QUOTA_SERVICE_ENDPOINT
24
+
25
+ # Get the amount of remaining quota points.
26
+ # ===Parameters
27
+ # <tt>module_id</tt>:: module_id of the service for which a quota request to be made, such as "VoiceButlerProduction"
28
+ def get_quota_information(module_id = "VoiceButlerSandbox")
29
+
30
+ response = invoke_authenticated("getQuotaInformation") do |message, doc|
31
+ message.add('moduleId', module_id)
32
+ end
33
+
34
+ quota_info = QuotaInformation.new(response)
35
+
36
+ return quota_info
37
+ end
38
+
39
+ # Changes the quota for a particular service
40
+ # ===Parameters
41
+ # <tt>module_id</tt>:: module_id of the service for which a quota request to be made, such as "VoiceButlerProduction"
42
+ # <tt>quota_max</tt>:: Quota limit to be set
43
+ def change_quota_pool(module_id = "VoiceButlerSandbox", quota_max = 100)
44
+
45
+ response = invoke_authenticated("changeQuotaPool") do |message, doc|
46
+ message.add('moduleId', module_id)
47
+ message.add('quotaMax', quota_max)
48
+ end
49
+
50
+ return BasicResponse.new(response)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ # Possible environment variables to choose a specific service environment.
2
+ #
3
+ # See also: https://www.developergarden.com/openapi/dokumentation/ansprache_der_services_ueber_webservice_schnittstellen#A.1.
4
+ class ServiceEnvironment
5
+
6
+ # Production environment constant
7
+ def self.PRODUCTION
8
+ @@PRODUCTION = 1
9
+ end
10
+
11
+ # Sandbox environment constant
12
+ def self.SANDBOX
13
+
14
+ @@SANDBOX = 2
15
+ end
16
+
17
+ # Mock environment constant
18
+ def self.MOCK
19
+ @@MOCK = 3
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # Represents a negative service response
2
+ class ServiceException < Exception
3
+ attr_accessor :response
4
+
5
+ def initialize(response)
6
+ @response = response
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '/../basic_response'
2
+
3
+ # Representing a response from the <tt>SmsService</tt>.
4
+ class SmsResponse < BasicResponse
5
+
6
+ # Constructor.
7
+ # ===Parameters
8
+ # <tt>response_xml</tt>:: Xml as returned by a <tt>sms_status</tt>-method call.
9
+ # <tt>raise_exception_on_error</tt>:: Xml as returned by a <tt>sms_status</tt>-method call.
10
+ def initialize(response_xml, raise_exception_on_error = true)
11
+ doc = response_xml.document
12
+
13
+ @error_code = doc.xpath("//status").text
14
+ @error_message = doc.xpath("//description").text
15
+
16
+ raise_on_error(response_xml) if raise_exception_on_error
17
+ end
18
+
19
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/../authenticated_service'
2
+ require File.dirname(__FILE__) + '/../sms_service/sms_response'
3
+
4
+ Handsoap.http_driver = :httpclient
5
+
6
+ # Print http and soap requests and reponses if ruby has been started with -d option.
7
+ Handsoap::Service.logger = $stdout if $DEBUG
8
+
9
+ module SmsService
10
+
11
+ # Client to the developer garden sms service.
12
+ # Can be used to send sms and flash sms.
13
+ #
14
+ # See also:
15
+ # * https://www.developergarden.com/openapi/dokumentation/services#4.2.3.
16
+ class SmsService < AuthenticatedService
17
+
18
+ @@SMS_SERVICE_ENDPOINT = {
19
+ :uri => "https://gateway.developer.telekom.com/p3gw-mod-odg-sms/services/SmsService",
20
+ :version => 1
21
+ }
22
+
23
+ endpoint @@SMS_SERVICE_ENDPOINT
24
+
25
+ # Send a sms.
26
+ # Detailed information about sending of sms can be found under:
27
+ # * https://www.developergarden.com/openapi/dokumentation/services#4.2.3.
28
+ # ===Parameters
29
+ # <tt>numbers</tt>:: Up to 10 receivers can be specified separated by commas (",").
30
+ # <tt>sms_message</tt>:: Actual message. Can be up to 765 characters. A sms will be charged for each 153 chars.
31
+ # <tt>originator</tt>:: String to be displayed as the originator of the message.
32
+ # Max. 11 characters. Further chars will be cut of. Allowed chars are [a-zA-Z0-9].
33
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment.
34
+ # <tt>account</tt>:: Currently unused
35
+ def send_sms(numbers, sms_message, originator, environment = 2, account = "")
36
+ return send_sms_common("sendSMS", numbers, sms_message, originator, environment, account)
37
+ end
38
+
39
+ # Send a flasg sms. A flash sms goes directly to the handy screen.
40
+ # Detailed information about sending of sms can be found under:
41
+ # * https://www.developergarden.com/openapi/dokumentation/services#4.2.3.
42
+ # ===Parameters
43
+ # <tt>numbers</tt>:: Up to 10 receivers can be specified separated by commas (",").
44
+ # <tt>sms_message</tt>:: Actual message. Can be up to 765 characters. A sms will be charged for each 153 chars.
45
+ # <tt>originator</tt>:: String to be displayed as the originator of the message.
46
+ # Max. 11 characters. Further chars will be cut of. Allowed chars are [a-zA-Z0-9].
47
+ # At least one letter needs to be present.
48
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment.
49
+ # <tt>account</tt>:: Currently unused
50
+ def send_flash_sms(numbers, sms_message, originator, environment = 2, account = "")
51
+ return send_sms_common("sendFlashSMS", numbers, sms_message, originator, environment, account)
52
+ end
53
+
54
+
55
+ protected
56
+
57
+ # Methods sendSMS and sendFlashSMS have identical arguments. They only differ in their action name.
58
+ # ===Parameters
59
+ # <tt>numbers</tt>:: Up to 10 receivers can be specified separated by commas (",").
60
+ # <tt>sms_message</tt>:: Actual message. Can be up to 765 characters. A sms will be charged for each 153 chars.
61
+ # <tt>originator</tt>:: String to be displayed as the originator of the message.
62
+ # Max. 11 characters. Further chars will be cut of. Allowed chars are [a-zA-Z0-9].
63
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment.
64
+ # <tt>account</tt>:: Currently unused
65
+ def send_sms_common(action_name, numbers, sms_message, originator, environment = 2, account = "")
66
+
67
+ # Cut originator down to 11 characters
68
+ originator = originator[0, 11]
69
+
70
+ response = invoke_authenticated(action_name) do |message, doc|
71
+ message.add("request")
72
+ request = message.find("request")
73
+ request.add('environment', environment)
74
+ request.add('number', numbers)
75
+ request.add('message', sms_message)
76
+ request.add('originator', originator)
77
+ request.add('account', account)
78
+ end
79
+
80
+ return SmsResponse.new(response)
81
+ end
82
+
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,15 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require File.dirname(__FILE__) + '/quota_service/quota_service'
5
+ require File.dirname(__FILE__) + '/voice_call_service/voice_call_service'
6
+ require File.dirname(__FILE__) + '/sms_service/sms_service'
7
+
8
+ module QuotaService
9
+ end
10
+
11
+ module SmsService
12
+ end
13
+
14
+ module VoiceCallService
15
+ end
@@ -0,0 +1,46 @@
1
+ require 'nokogiri'
2
+
3
+ module TokenService
4
+
5
+ # Verifies the timestamp of a security token to determine whether the token
6
+ # is yet or still valid.
7
+ # This is done to avoid unnecessary
8
+ class SecurityTokenValidator
9
+
10
+ # Verifies the timestamp of a security token to determine whether the token
11
+ # is yet or still valid.
12
+ # This is done to avoid unnecessary
13
+ # === Parameters
14
+ # <tt>security_token_xml</tt>:: Security token as plain xml.
15
+ # === Returns
16
+ # Boolean indicating whether the security token is yet or still valid.
17
+ def self.token_valid?(security_token_xml)
18
+ doc = Nokogiri.XML( security_token_xml )
19
+ conditions_node_set = doc.xpath("//schema:Conditions", "schema" => 'urn:oasis:names:tc:SAML:2.0:assertion')
20
+ conditions_elment = conditions_node_set.first
21
+ not_before = conditions_elment.get_attribute('NotBefore')
22
+ not_on_or_after = conditions_elment.get_attribute('NotOnOrAfter')
23
+
24
+ date_not_before = DateTime.parse(not_before)
25
+ date_not_on_or_after = DateTime.parse(not_on_or_after)
26
+
27
+ # Should be: date_not_before =< now < date_not_on_or_after
28
+ ret = DateTime.now.between?(date_not_before, date_not_on_or_after)
29
+
30
+ return ret
31
+ end
32
+
33
+ # Verifies the timestamp of a security token to determine whether the token
34
+ # is yet or still valid.
35
+ # This is done to avoid unnecessary
36
+ # === Parameters
37
+ # <tt>security_token_xml</tt>:: Security token as plain xml.
38
+ # === Returns
39
+ # (logical) not token_valid?(security_token_xml)
40
+ # Returns true if the security token is not valid!
41
+ def self.token_invalid?(security_token_xml)
42
+ return !(self.token_valid?(security_token_xml))
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,162 @@
1
+ require File.dirname(__FILE__) + '/../basic_service'
2
+ require File.dirname(__FILE__) + '/../token_service/security_token_validator'
3
+
4
+ Handsoap.http_driver = :httpclient
5
+
6
+ # Print http and soap requests and reponses if ruby has been started with -d option.
7
+ Handsoap::Service.logger = $stdout if $DEBUG
8
+
9
+ # Module defining a client to access the security token service.
10
+ module TokenService
11
+
12
+ # TokenService client to perform authentication against the security token service.
13
+ # Authentication is performed in two steps:
14
+ # 1. Call <tt>login</tt> to gather an intermediate token
15
+ # 2. Call <tt>getTokens</tt> to gather one or more security token(s).
16
+ # 3. Call any service method passing the security token(s) along the soap header.
17
+ # The security token service is an implementation of the OASIS WSS specification.
18
+ # See also: http://www.oasis-open.org/committees/wss/.
19
+ #
20
+ # Be aware that all security information is provided in the soap header not in the soap body.
21
+ # This is why a wss enabled service does not provide a separate method parameter to pass the security tokens.
22
+ # As mentioned before security is passed in the soap header, instead.
23
+ #
24
+ class TokenService < BasicService
25
+
26
+ @@TOKEN_SERVICE_ENDPOINT = {
27
+ :uri => 'https://sts.idm.telekom.com/TokenService',
28
+ :version => 1
29
+ }
30
+
31
+ endpoint @@TOKEN_SERVICE_ENDPOINT
32
+
33
+ # This is disabled per default because client time and server time need to be in sync
34
+ # to use this function. Otherwise local token verification might fail even on valid tokens.
35
+ # This would imply an unnecessary call to the token service.
36
+ @@PERFORM_LOCAL_TOKEN_CHECKS = false
37
+
38
+ # Create some namespace attributes
39
+ on_create_document do |doc|
40
+
41
+ # doc is of type XmlMason::Document
42
+ doc.alias 'ns1', "http://stss.idm.telekom.com/schema/"
43
+ doc.alias 'xmlns:ns2', "Security"
44
+ end
45
+
46
+ # Call the the security token service to gather an intermediate token.
47
+ def login
48
+ response = invoke("login") do |message|
49
+ doc = message.document
50
+
51
+ # Build the login header
52
+ build_login_header(doc)
53
+ end
54
+
55
+ intermediate_token = get_token_data_from_response(response)
56
+
57
+ return intermediate_token
58
+ end
59
+
60
+ #### Composite methods
61
+
62
+ # Check whether there is a security token. Authenticate if not.
63
+ # Reauthenticates if the security token has expired.
64
+ # ===Returns
65
+ # Security token as plain text/xml.
66
+ def get_security_token
67
+
68
+ # Reauthenticates if the security token has expired.
69
+ if @security_token.nil? then
70
+ authenticate
71
+ end
72
+
73
+ # Look at the validity dates of the token and locally check whether the token is still valid.
74
+ if @@PERFORM_LOCAL_TOKEN_CHECKS && SecurityTokenValidator.token_invalid?(@security_token) then
75
+ authenticate
76
+ end
77
+
78
+ return @security_token
79
+ end
80
+
81
+ protected
82
+
83
+ # Performs a two step authentication gathering an intermediate token and then the actual security token.
84
+ # The security token is used to make further service calls.
85
+ # ===Returns
86
+ # Security token as plain text/xml.
87
+ def authenticate
88
+ intermediate_token = login
89
+ @security_token = get_tokens(intermediate_token)
90
+
91
+ return @security_token
92
+ end
93
+
94
+ # Invokes the getTokens method using an intermediate token to gather security token(s).
95
+ # Security tokens are needed to perform further service calls.
96
+ # ===Parameters
97
+ # <tt>intermediate_token</tt>:: Intermediate token gathered by invoking the <tt>login</tt>-Method
98
+ def get_tokens(intermediate_token)
99
+ response = invoke("getTokens") do |message|
100
+ doc = message.document
101
+
102
+ # Build the login header
103
+ build_get_tokens_header(doc, intermediate_token)
104
+
105
+ message.add('ns1:serviceId', @@SERVICE_ID)
106
+ end
107
+
108
+ security_token = get_token_data_from_response(response)
109
+
110
+ return security_token
111
+ end
112
+
113
+ # Builds the header including the username/password token
114
+ # to call the login method in order to gather an intermediate token.
115
+ #
116
+ # The header of the given document will be enhanced so there is no need to
117
+ # process the returning value.
118
+ # ===Parameters
119
+ # <tt>doc</tt>:: Request XmlMason document.
120
+ def build_login_header(doc)
121
+
122
+ # Get the header element
123
+ header = build_security_header_common(doc)
124
+
125
+ security = header.find('Security')
126
+ security.add("UsernameToken")
127
+
128
+ # Create username section
129
+ username_token = security.find("UsernameToken")
130
+ username_token.add("Username")
131
+ username = username_token.find("Username")
132
+ username.set_value(@username)
133
+
134
+ # Create password section
135
+ username_token.add("Password")
136
+ password = username_token.find("Password")
137
+ password.set_attr("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText")
138
+ password.set_value(@password)
139
+
140
+ return header
141
+ end
142
+
143
+ # Build the header using the intermediate_token to gather the security token(s).
144
+ # This is part of the authentication process.
145
+ #
146
+ # The header of the given document will be enhanced so there is no need to
147
+ # process the returning value.
148
+ # ==Parameter
149
+ # <tt>doc</tt>:: Request XmlMason document.
150
+ # <tt>intermediate_token</tt>:: Intermediate token gathered by invoking the <tt>login</tt>-Method
151
+ def build_get_tokens_header(doc, intermediate_token)
152
+
153
+ # Get the header element
154
+ header = build_security_header_common(doc)
155
+ security = header.find("Security")
156
+ security.set_value( intermediate_token, :raw )
157
+
158
+ return header
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../basic_response'
2
+
3
+ # Represents a response for a voice call operation, such as <tt>new_call</tt> ot the
4
+ # VoiceCallService.
5
+ class CallStatusResponse < BasicResponse
6
+
7
+ attr_accessor :connection_time_a, :connection_time_b, :description_a, :description_b, :reason_a, :reason_b
8
+ attr_accessor :state_a, :state_b
9
+
10
+ # Constructor.
11
+ # ===Parameters
12
+ # <tt>response_xml</tt>:: Xml as returned by a <tt>call_status</tt>-method call.
13
+ # <tt>raise_exception_on_error</tt>:: Xml as returned by a <tt>call_status</tt>-method call.
14
+ def initialize(response_xml, raise_exception_on_error = true)
15
+ doc = response_xml.document
16
+
17
+ @error_code = doc.xpath("//status").text
18
+ @error_message = doc.xpath("//err_msg").text
19
+ @session_id = doc.xpath("//sessionId").text
20
+ @connection_time_a = doc.xpath("//connectiontimea").text
21
+ @connection_time_b = doc.xpath("//connectiontimeb").text
22
+ @description_a = doc.xpath("//descriptiona").text
23
+ @description_b = doc.xpath("//descriptionb").text
24
+ @reason_a = doc.xpath("//reasona").text
25
+ @reason_b = doc.xpath("//reasonb").text
26
+ @state_a = doc.xpath("//statea").text
27
+ @state_b = doc.xpath("//stateb").text
28
+
29
+ raise_on_error(response_xml) if raise_exception_on_error
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + '/../basic_response'
2
+
3
+ # Represents a response for a voice call operation, such as <tt>new_call</tt> ot the
4
+ # VoiceCallService.
5
+ class VoiceCallResponse < BasicResponse
6
+
7
+ attr_accessor :session_id
8
+
9
+ # Constructor.
10
+ # ===Parameters
11
+ # <tt>response_xml</tt>:: Xml as returned by a <tt>call_status</tt>-method call.
12
+ # <tt>raise_exception_on_error</tt>:: Xml as returned by a <tt>call_status</tt>-method call.
13
+ def initialize(response_xml, raise_exception_on_error = true)
14
+ doc = response_xml.document
15
+
16
+ @error_code = doc.xpath("//status").text
17
+ @error_message = doc.xpath("//err_msg").text
18
+ @session_id = doc.xpath("//sessionId").text
19
+
20
+ raise_on_error(response_xml) if raise_exception_on_error
21
+ end
22
+ end
@@ -0,0 +1,137 @@
1
+ require File.dirname(__FILE__) + '/../authenticated_service'
2
+ require File.dirname(__FILE__) + '/../service_environment'
3
+ require File.dirname(__FILE__) + '/../voice_call_service/voice_call_response'
4
+ require File.dirname(__FILE__) + '/../voice_call_service/call_status_response'
5
+
6
+ Handsoap.http_driver = :httpclient
7
+
8
+ # Print http and soap requests and reponses if ruby has been started with -d option.
9
+ Handsoap::Service.logger = $stdout if $DEBUG
10
+
11
+ module VoiceCallService
12
+
13
+ # Establish voice calls between two participants
14
+ # See also: http://www.developergarden.com/openapi/dokumentation/services#4.2.1.
15
+ class VoiceCallService < AuthenticatedService
16
+ @@VOICE_CALL_SERVICE_ENDPOINT = {
17
+ :uri => "https://gateway.developer.telekom.com/p3gw-mod-odg-voicebutler/services/VoiceButlerService",
18
+ :version => 1
19
+ }
20
+
21
+ endpoint @@VOICE_CALL_SERVICE_ENDPOINT
22
+
23
+ # Establish a voice call between two participants.
24
+ # After a connection to the first participant has been successfully established the seccond particiant is called.
25
+ # The call is established after the 2nd participant has picked up.
26
+ #
27
+ # ===Parameters
28
+ # <tt>a_number</tt>:: Phone number of participant a.
29
+ # <tt>b_number</tt>:: Phone number of participant b.
30
+ # <tt>expiration</tt>:: Nr of seconds until the call will be canceled if no <tt>call_status</tt> call is received.
31
+ # <tt>max_duration</tt>:: Maximum duration of the call in secons. In addition to this the system limit is applied.
32
+ # <tt>environment</tt>:: Service environment as defined in ServiceLevel.
33
+ # <tt>privacy_a</tt>:: Whether to show the phone number of participant a.
34
+ # <tt>privacy_b</tt>:: Whether to show the phone number of participant b.
35
+ # <tt>greeter</tt>:: Currently unused
36
+ # <tt>account</tt>:: Currently unused
37
+ def new_call(a_number, b_number, expiration, max_duration, environment = ServiceEnvironment.MOCK, privacy_a = false, privacy_b = false, greeter = "", account = "")
38
+ response = invoke_authenticated("newCall") do |message, doc|
39
+ message.add("request")
40
+ request = message.find("request")
41
+ request.add('environment', environment)
42
+ request.add('aNumber', a_number)
43
+ request.add('bNumber', b_number)
44
+ request.add('privacyA', privacy_a.to_s)
45
+ request.add('privacyB', privacy_b.to_s)
46
+ request.add('expiration', expiration)
47
+ request.add('maxDuration', max_duration)
48
+ request.add('greeter', greeter)
49
+ request.add('account', account)
50
+ end
51
+
52
+ return VoiceCallResponse.new(response)
53
+ end
54
+
55
+ # Retrieve information about a specific call.
56
+ # ===Parameters
57
+ # <tt>session_id</tt>:: Session id of the call of interest.
58
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment.
59
+ # <tt>keep_alive</tt>:: Prevent an expiration of the call by calling <tt>call_status</tt> with <tt>keep_alive = 1</tt>.
60
+ def call_status(session_id, environment = ServiceEnvironment.MOCK, keep_alive = 1)
61
+ response = invoke_authenticated("callStatus") do |message, doc|
62
+ message.add("request")
63
+ request = message.find("request")
64
+ request.add('environment', environment)
65
+ request.add('keepAlive', keep_alive)
66
+ request.add('sessionId', session_id)
67
+ end
68
+
69
+ return CallStatusResponse.new(response)
70
+ end
71
+
72
+ # Cancels an ongoing call.
73
+ # ===Parameters
74
+ # <tt>session_id</tt>::
75
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment.
76
+ def teardown_call(session_id, environment = ServiceEnvironment.MOCK)
77
+ response = invoke_authenticated("tearDownCall") do |message, doc|
78
+
79
+ # Add namespace
80
+ tdc = message.find("tearDownCall")
81
+ tdc.set_attr("xmlns", "http://webservice.voicebutler.odg.tonline.de")
82
+
83
+ message.add("request")
84
+ request = message.find("request")
85
+ request.add('environment', environment)
86
+ request.add('sessionId', session_id)
87
+ end
88
+
89
+ return CallStatusResponse.new(response)
90
+ end
91
+
92
+ # Establishes a voice call similar to <tt>newCall</tt>. <tt>b_number</tt> can be an array of numbers.
93
+ # The service will call participants listed in <tt>b_number</tt> in sequence until sb. picks up.
94
+ # ===Parameters
95
+ # <tt>a_number</tt>:: Phone number of participant a.
96
+ # <tt>b_number</tt>:: Phone number(s) of participant b. <tt>b_number</tt> can be an array of strings
97
+ # representing numbers or a single string representing a single number.
98
+ # <tt>expiration</tt>:: Nr of seconds until the call will be canceled if no <tt>call_status</tt> with keepalive=true call is received.
99
+ # <tt>max_duration</tt>:: Maximum duration of the call in secons. In addition to this the system limit is applied.
100
+ # <tt>environment</tt>:: Service environment as defined in ServiceEnvironment.
101
+ # <tt>privacy_a</tt>:: Whether to show the phone number of participant a.
102
+ # <tt>privacy_b</tt>:: Whether to show the phone number of participant b.
103
+ # <tt>max_wait</tt>:: Call the next participant after max_wait seconds.
104
+ # <tt>greeter</tt>:: Currently unused.
105
+ # <tt>account</tt>:: Currently unused.
106
+ def new_call_sequenced(a_number, b_number, expiration, max_duration, environment = ServiceEnvironment.MOCK, privacy_a = false, privacy_b = false, max_wait = 60, greeter = "", account = "")
107
+ response = invoke_authenticated("newCallSequenced") do |message, doc|
108
+ message.add("request")
109
+ request = message.find("request")
110
+ request.add('environment', environment)
111
+ request.add('aNumber', a_number)
112
+
113
+ # b_number can be an array of strings representing numbers or a single string representing a single number.
114
+ if b_number.is_a? Array then
115
+ # It's an array
116
+ for bn in b_number do
117
+ request.add('bNumber', bn)
118
+ end
119
+ else
120
+
121
+ # We assume its a string
122
+ request.add('bNumber', b_number)
123
+ end
124
+
125
+ request.add('privacyA', privacy_a.to_s)
126
+ request.add('privacyB', privacy_b.to_s)
127
+ request.add('expiration', expiration)
128
+ request.add('maxDuration', max_duration)
129
+ request.add('maxWait', max_wait)
130
+ request.add('greeter', greeter)
131
+ request.add('account', account)
132
+ end
133
+
134
+ return VoiceCallResponse.new(response)
135
+ end
136
+ end
137
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: developergarden_sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ platform: ruby
6
+ authors:
7
+ - Julian Fischer / Aperto move GmbH
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-04 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: httpclient
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.1.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.1
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: troelskn-handsoap
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.2.8
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: htmlentities
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 4.0.0
54
+ version:
55
+ description: Offers a ruby client for the open development services such as send SMS, voice call and quota management of the Deutsche Telekom AG. See also http://www.developergarden.com.
56
+ email: ruby@developergarden.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - README
63
+ - LICENSE
64
+ files:
65
+ - LICENSE
66
+ - README
67
+ - Rakefile
68
+ - lib/authenticated_service.rb
69
+ - lib/basic_response.rb
70
+ - lib/basic_service.rb
71
+ - lib/common
72
+ - lib/common/xml_tools.rb
73
+ - lib/quota_service
74
+ - lib/quota_service/quota_information.rb
75
+ - lib/quota_service/quota_service.rb
76
+ - lib/service_environment.rb
77
+ - lib/service_exception.rb
78
+ - lib/sms_service
79
+ - lib/sms_service/sms_response.rb
80
+ - lib/sms_service/sms_service.rb
81
+ - lib/telekom_sdk.rb
82
+ - lib/token_service
83
+ - lib/token_service/security_token_validator.rb
84
+ - lib/token_service/token_service.rb
85
+ - lib/voice_call_service
86
+ - lib/voice_call_service/call_status_response.rb
87
+ - lib/voice_call_service/voice_call_response.rb
88
+ - lib/voice_call_service/voice_call_service.rb
89
+ has_rdoc: true
90
+ homepage: http://www.developergarden.com
91
+ post_install_message:
92
+ rdoc_options: []
93
+
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
101
+ version:
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: "0"
107
+ version:
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.3.1
112
+ signing_key:
113
+ specification_version: 2
114
+ summary: Offers a ruby client for the open development services such as send SMS, voice call and quota management of the Deutsche Telekom AG. See also http://www.developergarden.com.
115
+ test_files: []
116
+