maestrano-ruby-test 0.8.3

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