maestrano-ruby-test 0.8.3

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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +45 -0
  5. data/LICENSE +21 -0
  6. data/README.md +794 -0
  7. data/Rakefile +40 -0
  8. data/bin/maestrano-console +9 -0
  9. data/lib/maestrano.rb +271 -0
  10. data/lib/maestrano/account/bill.rb +14 -0
  11. data/lib/maestrano/account/recurring_bill.rb +14 -0
  12. data/lib/maestrano/api/error/authentication_error.rb +8 -0
  13. data/lib/maestrano/api/error/base_error.rb +24 -0
  14. data/lib/maestrano/api/error/connection_error.rb +8 -0
  15. data/lib/maestrano/api/error/invalid_request_error.rb +14 -0
  16. data/lib/maestrano/api/list_object.rb +37 -0
  17. data/lib/maestrano/api/object.rb +187 -0
  18. data/lib/maestrano/api/operation/base.rb +215 -0
  19. data/lib/maestrano/api/operation/create.rb +18 -0
  20. data/lib/maestrano/api/operation/delete.rb +13 -0
  21. data/lib/maestrano/api/operation/list.rb +18 -0
  22. data/lib/maestrano/api/operation/update.rb +59 -0
  23. data/lib/maestrano/api/resource.rb +47 -0
  24. data/lib/maestrano/api/util.rb +122 -0
  25. data/lib/maestrano/open_struct.rb +11 -0
  26. data/lib/maestrano/saml/attribute_value.rb +15 -0
  27. data/lib/maestrano/saml/metadata.rb +64 -0
  28. data/lib/maestrano/saml/request.rb +93 -0
  29. data/lib/maestrano/saml/response.rb +201 -0
  30. data/lib/maestrano/saml/schemas/saml20assertion_schema.xsd +283 -0
  31. data/lib/maestrano/saml/schemas/saml20protocol_schema.xsd +302 -0
  32. data/lib/maestrano/saml/schemas/xenc_schema.xsd +146 -0
  33. data/lib/maestrano/saml/schemas/xmldsig_schema.xsd +318 -0
  34. data/lib/maestrano/saml/settings.rb +37 -0
  35. data/lib/maestrano/saml/validation_error.rb +7 -0
  36. data/lib/maestrano/sso.rb +86 -0
  37. data/lib/maestrano/sso/base_group.rb +31 -0
  38. data/lib/maestrano/sso/base_membership.rb +25 -0
  39. data/lib/maestrano/sso/base_user.rb +75 -0
  40. data/lib/maestrano/sso/group.rb +24 -0
  41. data/lib/maestrano/sso/session.rb +107 -0
  42. data/lib/maestrano/sso/user.rb +34 -0
  43. data/lib/maestrano/version.rb +3 -0
  44. data/lib/maestrano/xml_security/signed_document.rb +170 -0
  45. data/maestrano.gemspec +32 -0
  46. data/maestrano.png +0 -0
  47. data/test/helpers/api_helpers.rb +115 -0
  48. data/test/helpers/saml_helpers.rb +62 -0
  49. data/test/maestrano/account/bill_test.rb +48 -0
  50. data/test/maestrano/account/recurring_bill_test.rb +49 -0
  51. data/test/maestrano/api/list_object_test.rb +20 -0
  52. data/test/maestrano/api/object_test.rb +28 -0
  53. data/test/maestrano/api/resource_test.rb +343 -0
  54. data/test/maestrano/api/util_test.rb +31 -0
  55. data/test/maestrano/maestrano_test.rb +260 -0
  56. data/test/maestrano/open_struct_test.rb +10 -0
  57. data/test/maestrano/saml/request_test.rb +168 -0
  58. data/test/maestrano/saml/response_test.rb +290 -0
  59. data/test/maestrano/saml/settings_test.rb +51 -0
  60. data/test/maestrano/sso/base_group_test.rb +54 -0
  61. data/test/maestrano/sso/base_membership_test.rb +45 -0
  62. data/test/maestrano/sso/base_user_test.rb +114 -0
  63. data/test/maestrano/sso/group_test.rb +47 -0
  64. data/test/maestrano/sso/session_test.rb +161 -0
  65. data/test/maestrano/sso/user_test.rb +65 -0
  66. data/test/maestrano/sso_test.rb +105 -0
  67. data/test/maestrano/xml_security/signed_document.rb +163 -0
  68. data/test/support/saml/certificates/certificate1 +12 -0
  69. data/test/support/saml/certificates/r1_certificate2_base64 +1 -0
  70. data/test/support/saml/responses/adfs_response_sha1.xml +46 -0
  71. data/test/support/saml/responses/adfs_response_sha256.xml +46 -0
  72. data/test/support/saml/responses/adfs_response_sha384.xml +46 -0
  73. data/test/support/saml/responses/adfs_response_sha512.xml +46 -0
  74. data/test/support/saml/responses/no_signature_ns.xml +48 -0
  75. data/test/support/saml/responses/open_saml_response.xml +56 -0
  76. data/test/support/saml/responses/r1_response6.xml.base64 +1 -0
  77. data/test/support/saml/responses/response1.xml.base64 +1 -0
  78. data/test/support/saml/responses/response2.xml.base64 +79 -0
  79. data/test/support/saml/responses/response3.xml.base64 +66 -0
  80. data/test/support/saml/responses/response4.xml.base64 +93 -0
  81. data/test/support/saml/responses/response5.xml.base64 +102 -0
  82. data/test/support/saml/responses/response_with_ampersands.xml +139 -0
  83. data/test/support/saml/responses/response_with_ampersands.xml.base64 +93 -0
  84. data/test/support/saml/responses/response_with_multiple_attribute_values.xml +57 -0
  85. data/test/support/saml/responses/simple_saml_php.xml +71 -0
  86. data/test/support/saml/responses/starfield_response.xml.base64 +1 -0
  87. data/test/support/saml/responses/wrapped_response_2.xml.base64 +150 -0
  88. data/test/test_helper.rb +47 -0
  89. metadata +315 -0
@@ -0,0 +1,187 @@
1
+ module Maestrano
2
+ module API
3
+ class Object
4
+ include Enumerable
5
+
6
+ attr_accessor :api_token
7
+ @@permanent_attributes = Set.new([:api_token, :id])
8
+
9
+ # The default :id method is deprecated and isn't useful to us
10
+ if method_defined?(:id)
11
+ undef :id
12
+ end
13
+
14
+ def initialize(id=nil, api_token=nil)
15
+ # parameter overloading!
16
+ if id.kind_of?(Hash)
17
+ @retrieve_options = id.dup
18
+ @retrieve_options.delete(:id)
19
+ id = id[:id]
20
+ else
21
+ @retrieve_options = {}
22
+ end
23
+
24
+ @api_token = api_token
25
+ @values = {}
26
+ # This really belongs in API::Resource, but not putting it there allows us
27
+ # to have a unified inspect method
28
+ @unsaved_values = Set.new
29
+ @transient_values = Set.new
30
+ @values[:id] = id if id
31
+ end
32
+
33
+ def self.construct_from(values, api_token=nil)
34
+ obj = self.new(values[:id], api_token)
35
+ obj.refresh_from(values, api_token)
36
+ obj
37
+ end
38
+
39
+ def to_s(*args)
40
+ JSON.pretty_generate(@values)
41
+ end
42
+
43
+ def inspect()
44
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
45
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values)
46
+ end
47
+
48
+ def refresh_from(values, api_token, partial=false)
49
+ @api_token = api_token
50
+
51
+ @previous_metadata = values[:metadata]
52
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
53
+ added = Set.new(values.keys - @values.keys)
54
+ # Wipe old state before setting new.
55
+
56
+ instance_eval do
57
+ remove_accessors(removed)
58
+ add_accessors(added)
59
+ end
60
+ removed.each do |k|
61
+ @values.delete(k)
62
+ @transient_values.add(k)
63
+ @unsaved_values.delete(k)
64
+ end
65
+ values.each do |k, v|
66
+ @values[k] = Util.convert_to_maestrano_object(v, api_token)
67
+ @transient_values.delete(k)
68
+ @unsaved_values.delete(k)
69
+ end
70
+ end
71
+
72
+ def [](k)
73
+ @values[k.to_sym]
74
+ end
75
+
76
+ def []=(k, v)
77
+ send(:"#{k}=", v)
78
+ end
79
+
80
+ def keys
81
+ @values.keys
82
+ end
83
+
84
+ def values
85
+ @values.values
86
+ end
87
+
88
+ def to_json(*a)
89
+ JSON.generate(@values)
90
+ end
91
+
92
+ def as_json(*a)
93
+ @values.as_json(*a)
94
+ end
95
+
96
+ def to_hash
97
+ @values
98
+ end
99
+
100
+ def each(&blk)
101
+ @values.each(&blk)
102
+ end
103
+
104
+ def _dump(level)
105
+ Marshal.dump([@values, @api_token])
106
+ end
107
+
108
+ def self._load(args)
109
+ values, api_token = Marshal.load(args)
110
+ construct_from(values, api_token)
111
+ end
112
+
113
+ if RUBY_VERSION < '1.9.2'
114
+ def respond_to?(symbol)
115
+ @values.has_key?(symbol) || super
116
+ end
117
+ end
118
+
119
+ protected
120
+
121
+ def metaclass
122
+ class << self; self; end
123
+ end
124
+
125
+ def remove_accessors(keys)
126
+ metaclass.instance_eval do
127
+ keys.each do |k|
128
+ next if @@permanent_attributes.include?(k)
129
+ k_eq = :"#{k}="
130
+ remove_method(k) if method_defined?(k)
131
+ remove_method(k_eq) if method_defined?(k_eq)
132
+ end
133
+ end
134
+ end
135
+
136
+ def add_accessors(keys)
137
+ metaclass.instance_eval do
138
+ keys.each do |k|
139
+ next if @@permanent_attributes.include?(k)
140
+ k_eq = :"#{k}="
141
+ define_method(k) { @values[k] }
142
+ define_method(k_eq) do |v|
143
+ if v == ""
144
+ raise ArgumentError.new(
145
+ "You cannot set #{k} to an empty string." +
146
+ "We interpret empty strings as nil in requests." +
147
+ "You may set #{self}.#{k} = nil to delete the property.")
148
+ end
149
+ @values[k] = v
150
+ @unsaved_values.add(k)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ def method_missing(name, *args)
157
+ # TODO: only allow setting in updateable classes.
158
+ if name.to_s.end_with?('=')
159
+ attr = name.to_s[0...-1].to_sym
160
+ add_accessors([attr])
161
+ begin
162
+ mth = method(name)
163
+ rescue NameError
164
+ raise NoMethodError.new("Cannot set #{attr} on this object. HINT: you can't set: #{@@permanent_attributes.to_a.join(', ')}")
165
+ end
166
+ return mth.call(args[0])
167
+ else
168
+ return @values[name] if @values.has_key?(name)
169
+ end
170
+
171
+ begin
172
+ super
173
+ rescue NoMethodError => e
174
+ if @transient_values.include?(name)
175
+ raise NoMethodError.new(e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Maestrano's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}")
176
+ else
177
+ raise
178
+ end
179
+ end
180
+ end
181
+
182
+ def respond_to_missing?(symbol, include_private = false)
183
+ @values.has_key?(symbol) || super
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,215 @@
1
+ module Maestrano
2
+ module API
3
+ module Operation
4
+ module Base
5
+ # class << self
6
+ # attr_accessor :api_token, :api_base, :verify_ssl_certs, :api_version
7
+ # end
8
+
9
+ def self.api_url(url='')
10
+ Maestrano.param('api.host') + Maestrano.param('api.base') + url
11
+ end
12
+
13
+ # Perform remote request
14
+ def self.request(method, url, api_token, params={}, headers={})
15
+ unless api_token ||= Maestrano.param('api_token')
16
+ raise Maestrano::API::Error::AuthenticationError.new('No API key provided.')
17
+ end
18
+
19
+ request_opts = { :verify_ssl => false }
20
+
21
+ if self.ssl_preflight_passed?
22
+ request_opts.update(
23
+ verify_ssl: OpenSSL::SSL::VERIFY_PEER,
24
+ ssl_ca_file: Maestrano.param('ssl_bundle_path')
25
+ )
26
+ end
27
+
28
+ params = Util.objects_to_ids(params)
29
+ url = api_url(url)
30
+
31
+ case method.to_s.downcase.to_sym
32
+ when :get, :head, :delete
33
+ # Make params into GET parameters
34
+ url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
35
+ payload = nil
36
+ else
37
+ payload = uri_encode(params)
38
+ end
39
+
40
+ request_opts.update(:headers => request_headers(api_token).update(headers),
41
+ :method => method, :open_timeout => 30,
42
+ :payload => payload, :url => url, :timeout => 80)
43
+
44
+ begin
45
+ response = execute_request(request_opts)
46
+ rescue SocketError => e
47
+ handle_restclient_error(e)
48
+ rescue NoMethodError => e
49
+ # Work around RestClient bug
50
+ if e.message =~ /\WRequestFailed\W/
51
+ e = APIConnectionError.new('Unexpected HTTP response code')
52
+ handle_restclient_error(e)
53
+ else
54
+ raise
55
+ end
56
+ rescue RestClient::ExceptionWithResponse => e
57
+ if rcode = e.http_code and rbody = e.http_body
58
+ handle_api_error(rcode, rbody)
59
+ else
60
+ handle_restclient_error(e)
61
+ end
62
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
63
+ handle_restclient_error(e)
64
+ end
65
+
66
+ [parse(response), api_token]
67
+ end
68
+
69
+ private
70
+
71
+ def self.ssl_preflight_passed?
72
+ if !Maestrano.param('api.verify_ssl_certs')
73
+ #$stderr.puts "WARNING: Running without SSL cert verification. " +
74
+ # "Execute 'Maestrano.configure { |config| config.verify_ssl_certs = true' } to enable verification."
75
+ return false
76
+ elsif !Util.file_readable(Maestrano.param('ssl_bundle_path'))
77
+ $stderr.puts "WARNING: Running without SSL cert verification " +
78
+ "because #{Maestrano.param('ssl_bundle_path')} isn't readable"
79
+
80
+ return false
81
+ end
82
+
83
+ return true
84
+ end
85
+
86
+ def self.user_agent
87
+ @uname ||= get_uname
88
+
89
+ {
90
+ :bindings_version => Maestrano.param('api.version'),
91
+ :lang => Maestrano.param('api.lang'),
92
+ :lang_version => Maestrano.param('api.lang_version'),
93
+ :platform => RUBY_PLATFORM,
94
+ :publisher => 'maestrano',
95
+ :uname => @uname
96
+ }
97
+
98
+ end
99
+
100
+ def self.get_uname
101
+ `uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
102
+ rescue Errno::ENOMEM => ex # couldn't create subprocess
103
+ "uname lookup failed"
104
+ end
105
+
106
+ def self.uri_encode(params)
107
+ Util.flatten_params(params).
108
+ map { |k,v| "#{k}=#{Util.url_encode(v)}" }.join('&')
109
+ end
110
+
111
+ def self.request_headers(api_token)
112
+ headers = {
113
+ :user_agent => "Maestrano/v1 RubyBindings/#{Maestrano.param('api.version')}",
114
+ :authorization => "Basic #{Base64.encode64(api_token)}",
115
+ :content_type => 'application/x-www-form-urlencoded'
116
+ }
117
+
118
+ api_version = Maestrano.param('api_version')
119
+ headers[:maestrano_version] = api_version if api_version
120
+
121
+ begin
122
+ headers.update(:x_maestrano_client_user_agent => JSON.generate(user_agent))
123
+ rescue => e
124
+ headers.update(:x_maestrano_client_raw_user_agent => user_agent.inspect,
125
+ :error => "#{e} (#{e.class})")
126
+ end
127
+ end
128
+
129
+ def self.execute_request(opts)
130
+ RestClient::Request.execute(opts)
131
+ end
132
+
133
+ def self.parse(response)
134
+ begin
135
+ # Would use :symbolize_names => true, but apparently there is
136
+ # some library out there that makes symbolize_names not work.
137
+ response = JSON.parse(response.body)
138
+ rescue JSON::ParserError
139
+ raise general_api_error(response.code, response.body)
140
+ end
141
+
142
+ response = Util.symbolize_names(response)
143
+ response[:data]
144
+ end
145
+
146
+ def self.general_api_error(rcode, rbody)
147
+ Maestrano::API::Error::BaseError.new("Invalid response object from API: #{rbody.inspect} " +
148
+ "(HTTP response code was #{rcode})", rcode, rbody)
149
+ end
150
+
151
+ def self.handle_api_error(rcode, rbody)
152
+ begin
153
+ error_obj = JSON.parse(rbody)
154
+ error_obj = Util.symbolize_names(error_obj)
155
+ errors = error_obj[:errors] or raise Maestrano::API::Error::BaseError.new # escape from parsing
156
+
157
+ rescue JSON::ParserError, Maestrano::API::Error::BaseError
158
+ raise general_api_error(rcode, rbody)
159
+ end
160
+
161
+ case rcode
162
+ when 400, 404
163
+ raise invalid_request_error(errors, rcode, rbody, error_obj)
164
+ when 401
165
+ raise authentication_error(errors, rcode, rbody, error_obj)
166
+ else
167
+ raise api_error(errors, rcode, rbody, error_obj)
168
+ end
169
+
170
+ end
171
+
172
+ def self.invalid_request_error(errors, rcode, rbody, error_obj)
173
+ Maestrano::API::Error::InvalidRequestError.new(errors.first.join(" "), errors.keys.first.to_s, rcode,
174
+ rbody, error_obj)
175
+ end
176
+
177
+ def self.authentication_error(errors, rcode, rbody, error_obj)
178
+ Maestrano::API::Error::AuthenticationError.new(errors.first.join(" "), rcode, rbody, error_obj)
179
+ end
180
+
181
+ def self.api_error(errors, rcode, rbody, error_obj)
182
+ Maestrano::API::Error::BaseError.new(errors[:message], rcode, rbody, error_obj)
183
+ end
184
+
185
+ def self.handle_restclient_error(e)
186
+ case e
187
+ when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
188
+ message = "Could not connect to Maestrano. " +
189
+ "Please check your internet connection and try again. " +
190
+ "If this problem persists, you should check Maestrano service status at " +
191
+ "https://twitter.com/maestrano, or let us know at support@maestrano.com."
192
+
193
+ when RestClient::SSLCertificateNotVerified
194
+ message = "Could not verify Maestrano's SSL certificate. " +
195
+ "Please make sure that your network is not intercepting certificates. " +
196
+ "(Try going to https://maestrano.com/api/v1/ping in your browser.) " +
197
+ "If this problem persists, let us know at support@maestrano.com."
198
+
199
+ when SocketError
200
+ message = "Unexpected error communicating when trying to connect to Maestrano. " +
201
+ "You may be seeing this message because your DNS is not working. " +
202
+ "To check, try running 'host maestrano.com' from the command line."
203
+
204
+ else
205
+ message = "Unexpected error communicating with Maestrano. " +
206
+ "If this problem persists, let us know at support@maestrano.com."
207
+
208
+ end
209
+
210
+ raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,18 @@
1
+ module Maestrano
2
+ module API
3
+ module Operation
4
+ module Create
5
+ module ClassMethods
6
+ def create(params={}, api_token=nil)
7
+ response, api_token = Maestrano::API::Operation::Base.request(:post, self.url, api_token, params)
8
+ Util.convert_to_maestrano_object(response, api_token)
9
+ end
10
+ end
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Maestrano
2
+ module API
3
+ module Operation
4
+ module Delete
5
+ def delete(params = {})
6
+ response, api_token = Maestrano::API::Operation::Base.request(:delete, url, @api_token, params)
7
+ refresh_from(response, api_token)
8
+ self
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Maestrano
2
+ module API
3
+ module Operation
4
+ module List
5
+ module ClassMethods
6
+ def all(filters={}, api_token=nil)
7
+ response, api_token = Maestrano::API::Operation::Base.request(:get, url, api_token, filters)
8
+ Util.convert_to_maestrano_object(response, api_token)
9
+ end
10
+ end
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end