developergarden_sdk 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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
+