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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7dd07e563cd1bfdcf5be47e4a3eadc1e8c14165b
4
+ data.tar.gz: 70a897858b0cc2f874d6ebb4df56724a2d406868
5
+ SHA512:
6
+ metadata.gz: 7b062f0700cd0c0066ee27f18c64f2e7555f6f2d77ab305613227f94098eaf1a518fff3f5ecdd5cc88c549e944aa894c267417bb4425678e083418c0279d8fd2
7
+ data.tar.gz: 844d786b1bc77a8d8a0a94920f907d22c657c22a3fcfabb45ddeacb6b4eba67e24db710d0f86e82f2062a26716313938dc5cb17a8416da1676110f237755caa4
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ maestrano (0.8.2)
5
+ json (~> 1.8)
6
+ mime-types (~> 1.25)
7
+ nokogiri (>= 1.5.0)
8
+ rest-client (~> 1.4)
9
+ uuid (~> 2.3)
10
+
11
+ GEM
12
+ remote: http://rubygems.org/
13
+ specs:
14
+ json (1.8.1)
15
+ macaddr (1.7.1)
16
+ systemu (~> 2.6.2)
17
+ metaclass (0.0.4)
18
+ mime-types (1.25.1)
19
+ mini_portile (0.6.0)
20
+ mocha (0.13.3)
21
+ metaclass (~> 0.0.1)
22
+ netrc (0.7.7)
23
+ nokogiri (1.6.3.1)
24
+ mini_portile (= 0.6.0)
25
+ rake (10.3.2)
26
+ rest-client (1.7.2)
27
+ mime-types (>= 1.16, < 3.0)
28
+ netrc (~> 0.7)
29
+ shoulda (2.11.3)
30
+ systemu (2.6.4)
31
+ test-unit (2.5.5)
32
+ timecop (0.6.0)
33
+ uuid (2.3.7)
34
+ macaddr (~> 1.0)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ maestrano!
41
+ mocha (~> 0.13)
42
+ rake (~> 10)
43
+ shoulda (~> 2.11)
44
+ test-unit (~> 2)
45
+ timecop (<= 0.6.0)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2014 Maestrano Pty. Ltd.
2
+
3
+ The MIT License (MIT)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,794 @@
1
+ <p align="center">
2
+ <img src="https://raw.github.com/maestrano/maestrano-rails/master/maestrano.png" alt="Maestrano Logo">
3
+ </p>
4
+
5
+ Maestrano Cloud Integration is currently in closed beta. Want to know more? Send us an email to <contact@maestrano.com>.
6
+
7
+
8
+
9
+ - - -
10
+
11
+ 1. [Getting Setup](#getting-setup)
12
+ 2. [Getting Started with Rails](#getting-started-with-rails)
13
+ 3. [Getting Started](#getting-started)
14
+ * [Installation](#installation)
15
+ * [Configuration](#configuration)
16
+ * [Metadata Endpoint](#metadata-endpoint)
17
+ 4. [Single Sign-On Setup](#single-sign-on-setup)
18
+ * [User Setup](#user-setup)
19
+ * [Group Setup](#group-setup)
20
+ * [Controller Setup](#controller-setup)
21
+ * [Other Controllers](#other-controllers)
22
+ 5. [Account Webhooks](#account-webhooks)
23
+ * [Groups Controller](#groups-controller-service-cancellation)
24
+ * [Group Users Controller](#group-users-controller-business-member-removal)
25
+ 6. [API](#api)
26
+ * [Bill](#bill)
27
+ * [Recurring Bill](#recurring-bill)
28
+
29
+ - - -
30
+
31
+ ## Getting Setup
32
+ Before integrating with us you will need an App ID and API Key. Maestrano Cloud Integration being still in closed beta you will need to contact us beforehand to gain production access.
33
+
34
+ For testing purpose we provide an API Sandbox where you can freely obtain an App ID and API Key. The sandbox is great to test single sign-on and API integration (e.g: billing API).
35
+
36
+ To get started just go to: http://api-sandbox.maestrano.io
37
+
38
+ ## Getting Started with Rails
39
+
40
+ If you're looking at integrating Maestrano in your Rails application then you should use the maestrano-rails gem.
41
+
42
+ More details on the [maestrano-rails project page](https://github.com/maestrano/maestrano-rails).
43
+
44
+ ## Getting Started
45
+
46
+ ### Installation
47
+
48
+ To install the gem run
49
+ ```console
50
+ gem install maestrano
51
+ ```
52
+
53
+ Or add it to your Gemfile
54
+ ```ruby
55
+ gem 'maestrano'
56
+ ```
57
+
58
+
59
+ ### Configuration
60
+ Once installed the first step is to create an initializer to configure the behaviour of the Maestrano gem - including setting your API key.
61
+
62
+ The initializer should look like this:
63
+ ```ruby
64
+ # Use this block to configure the behaviour of Maestrano
65
+ # in your app
66
+ Maestrano.configure do |config|
67
+
68
+ # ==> Environment configuration
69
+ # The environment to connect to.
70
+ # If set to 'production' then all Single Sign-On (SSO) and API requests
71
+ # will be made to maestrano.com
72
+ # If set to 'test' then requests will be made to api-sandbox.maestrano.io
73
+ # The api-sandbox allows you to easily test integration scenarios.
74
+ # More details on http://api-sandbox.maestrano.io
75
+ #
76
+ config.environment = 'test' # or 'production'
77
+
78
+ # ==> Application host
79
+ # This is your application host (e.g: my-app.com) which is ultimately
80
+ # used to redirect users to the right SAML url during SSO handshake.
81
+ #
82
+ config.app.host = (config.environment == 'production' ? 'https://my-app.com' : 'http://localhost:3000')
83
+
84
+ # ==> App ID & API key
85
+ # Your application App ID and API key which you can retrieve on http://maestrano.com
86
+ # via your cloud partner dashboard.
87
+ # For testing you can retrieve/generate an api.id and api.key from the API Sandbox directly
88
+ # on http://api-sandbox.maestrano.io
89
+ #
90
+ config.api.id = (config.environment == 'production' ? 'prod_app_id' : 'sandbox_app_id')
91
+ config.api.key = (config.environment == 'production' ? 'prod_api_key' : 'sandbox_api_key')
92
+
93
+ # ==> Single Sign-On activation
94
+ # Enable/Disable single sign-on. When troubleshooting authentication issues
95
+ # you might want to disable SSO temporarily
96
+ #
97
+ # config.sso.enabled = true
98
+
99
+ # ==> Single Sign-On Identity Manager
100
+ # By default we consider that the domain managing user identification
101
+ # is the same as your application host (see above config.app.host parameter)
102
+ # If you have a dedicated domain managing user identification and therefore
103
+ # responsible for the single sign-on handshake (e.g: https://idp.my-app.com)
104
+ # then you can specify it below
105
+ #
106
+ # config.sso.idm = (config.environment == 'production' ? 'https://idp.my-app.com' : 'http://localhost:3000')
107
+
108
+ # ==> SSO Initialization endpoint
109
+ # This is your application path to the SAML endpoint that allows users to
110
+ # initialize SSO authentication. Upon reaching this endpoint users your
111
+ # application will automatically create a SAML request and redirect the user
112
+ # to Maestrano. Maestrano will then authenticate and authorize the user. Upon
113
+ # authorization the user gets redirected to your application consumer endpoint
114
+ # (see below) for initial setup and/or login.
115
+ #
116
+ # config.sso.init_path = '/maestrano/auth/saml/init'
117
+
118
+ # ==> SSO Consumer endpoint
119
+ # This is your application path to the SAML endpoint that allows users to
120
+ # finalize SSO authentication. During the 'consume' action your application
121
+ # sets users (and associated group) up and/or log them in.
122
+ #
123
+ # config.sso.consume_path = '/maestrano/auth/saml/consume'
124
+
125
+ # ==> Single Logout activation
126
+ # Enable/Disable single logout. When troubleshooting authentication issues
127
+ # you might want to disable SLO temporarily.
128
+ # If set to false then Maestrano::SSO::Session#valid? - which should be
129
+ # used in a controller before filter to check user session - always return true
130
+ #
131
+ # config.sso.slo_enabled = true
132
+
133
+ # ==> SSO User creation mode
134
+ # !IMPORTANT
135
+ # On Maestrano users can take several "instances" of your service. You can consider
136
+ # each "instance" as 1) a billing entity and 2) a collaboration group (this is
137
+ # equivalent to a 'customer account' in a commercial world). When users login to
138
+ # your application via single sign-on they actually login via a specific group which
139
+ # is then supposed to determine which data they have access to inside your application.
140
+ #
141
+ # E.g: John and Jack are part of group 1. They should see the same data when they login to
142
+ # your application (employee info, analytics, sales etc..). John is also part of group 2
143
+ # but not Jack. Therefore only John should be able to see the data belonging to group 2.
144
+ #
145
+ # In most application this is done via collaboration/sharing/permission groups which is
146
+ # why a group is required to be created when a new user logs in via a new group (and
147
+ # also for billing purpose - you charge a group, not a user directly).
148
+ #
149
+ # == mode: 'real'
150
+ # In an ideal world a user should be able to belong to several groups in your application.
151
+ # In this case you would set the 'sso.creation_mode' to 'real' which means that the uid
152
+ # and email we pass to you are the actual user email and maestrano universal id.
153
+ #
154
+ # == mode: 'virtual'
155
+ # Now let's say that due to technical constraints your application cannot authorize a user
156
+ # to belong to several groups. Well next time John logs in via a different group there will
157
+ # be a problem: the user already exists (based on uid or email) and cannot be assigned
158
+ # to a second group. To fix this you can set the 'sso.creation_mode' to 'virtual'. In this
159
+ # mode users get assigned a truly unique uid and email across groups. So next time John logs
160
+ # in a whole new user account can be created for him without any validation problem. In this
161
+ # mode the email we assign to him looks like "usr-sdf54.cld-45aa2@mail.maestrano.com". But don't
162
+ # worry we take care of forwarding any email you would send to this address
163
+ #
164
+ # config.sso.creation_mode = 'real' # or 'virtual'
165
+
166
+ # ==> Account Webhooks
167
+ # Single sign on has been setup into your app and Maestrano users are now able
168
+ # to use your service. Great! Wait what happens when a business (group) decides to
169
+ # stop using your service? Also what happens when a user gets removed from a business?
170
+ # Well the endpoints below are for Maestrano to be able to notify you of such
171
+ # events.
172
+ #
173
+ # Even if the routes look restful we issue only issue DELETE requests for the moment
174
+ # to notify you of any service cancellation (group deletion) or any user being
175
+ # removed from a group.
176
+ #
177
+ # config.webhook.account.groups_path = '/maestrano/account/groups/:id',
178
+ # config.webhook.account.group_users_path = '/maestrano/account/groups/:group_id/users/:id',
179
+ end
180
+ ```
181
+
182
+ ### Metadata Endpoint
183
+ Your configuration initializer is now all setup and shiny. Great! But need to know about it. Of course
184
+ we could propose a long and boring form on maestrano.com for you to fill all these details (especially the webhooks) but we thought it would be more convenient to fetch that automatically.
185
+
186
+ For that we expect you to create a metadata endpoint that we can fetch regularly (or when you press 'refresh metadata' in your maestrano cloud partner dashboard). By default we assume that it will be located at
187
+ YOUR_WEBSITE/maestrano/metadata(.json)
188
+
189
+ Of course if you prefer a different url you can always change that endpoint in your maestrano cloud partner dashboard.
190
+
191
+ What would the controller action look like? First let's talk about authentication. You don't want that endpoint to be visible to anyone. Maestrano always uses http basic authentication to contact your service remotely. The login/password used for this authentication are your actual api.id and api.key.
192
+
193
+ So here is an example of controller action for Rails to adapt depending on the framework you're using:
194
+
195
+ ```ruby
196
+ class MaestranoMetaDataController < ApplicationController
197
+ before_filter :authenticate_maestrano!
198
+
199
+ def metadata
200
+ render json: Maestrano.to_metadata
201
+ end
202
+
203
+ private
204
+ def authenticate_maestrano!
205
+ authorized = false
206
+ authenticate_with_http_basic do |app_id, api_token|
207
+ authorized = Maestrano.authenticate(app_id,api_token)
208
+ end
209
+ unless authorized
210
+ render json: {error: 'Invalid credentials' }, status: :unauthorized
211
+ end
212
+ return true
213
+ end
214
+ end
215
+ ```
216
+
217
+ ## Single Sign-On Setup
218
+ In order to get setup with single sign-on you will need a user model and a group model. It will also require you to write a controller for the init phase and consume phase of the single sign-on handshake.
219
+
220
+ You might wonder why we need a 'group' on top of a user. Well Maestrano works with businesses and as such expects your service to be able to manage groups of users. A group represents 1) a billing entity 2) a collaboration group. During the first single sign-on handshake both a user and a group should be created. Additional users logging in via the same group should then be added to this existing group (see controller setup below)
221
+
222
+ ### User Setup
223
+ Let's assume that your user model is called 'User'. The best way to get started with SSO is to define a class method on this model called 'find_or_create_for_maestrano' accepting a hash of attributes - provided by Maestrano - and aiming at either finding an existing maestrano user in your database or creating a new one. Your user model should also have a :provider attribute and a :uid attribute used to identify the source of the user - Maestrano, LinkedIn, AngelList etc..
224
+
225
+ Assuming the above the method could look like this:
226
+ ```ruby
227
+ # Only if you need to set a random password
228
+ require 'digest/sha1'
229
+
230
+ class User
231
+
232
+ ...
233
+
234
+ def self.find_or_create_for_maestrano(sso_hash)
235
+ user = self.where(provider:'maestrano', uid: sso_hash[:uid]).first
236
+
237
+ unless user
238
+ user = self.new
239
+
240
+ # Mapping
241
+ user.provider = 'maestrano'
242
+ user.uid = sso_hash[:uid]
243
+ user.name = sso_hash[:info][:first_name]
244
+ user.surname = sso_hash[:info][:last_name]
245
+ user.email = sso_hash[:info][:email]
246
+ # user.country_alpha2 = sso_hash[:info][:country]
247
+ # user.company = sso_hash[:info][:company_name]
248
+ # user.password = Digest::SHA1.hexdigest("#{Time.now}-#{rand(100)}")[0..20]
249
+ # user.password_confirmation = user.password
250
+ # user.some_other_required_field = 'some-appropriate-default-value'
251
+
252
+ # Save the user
253
+ user.save
254
+ end
255
+
256
+ return user
257
+ end
258
+
259
+ ...
260
+
261
+ end
262
+ ```
263
+
264
+ ### Group Setup
265
+ The group setup is similar to the user one. The mapping is a little easier though. Your model should also have the :provider and :uid attributes. Also your group model should have a add_member method and also a has_member? method (see controller below)
266
+
267
+ Assuming a group model called 'Organization', the find_or_create_for_maestrano class method could look like this:
268
+ ```ruby
269
+ class Organization
270
+
271
+ ...
272
+
273
+ def self.find_or_create_for_maestrano(sso_hash)
274
+ organization = self.where(provider:'maestrano', uid: sso_hash[:uid]).first
275
+
276
+ unless organization
277
+ organization = self.new
278
+
279
+ # Mapping
280
+ organization.provider = 'maestrano'
281
+ organization.uid = sso_hash[:uid]
282
+ organization.name = sso_hash[:info][:company_name] || 'Some default'
283
+ # organization.country_alpha2 = sso_hash[:info][:country]
284
+ # organization.free_trial_end_at = sso_hash[:info][:free_trial_end_at]
285
+
286
+ # Save the organization
287
+ organization.save
288
+ end
289
+
290
+ return organization
291
+ end
292
+
293
+ ...
294
+
295
+ end
296
+ ```
297
+
298
+ ### Controller Setup
299
+ Your controller will need to have two actions: init and consume. The init action will initiate the single sign-on request and redirect the user to Maestrano. The consume action will receive the single sign-on response, process it and match/create the user and the group.
300
+
301
+ The init action is all handled via Maestrano methods and should look like this:
302
+ ```ruby
303
+ def init
304
+ redirect_to Maestrano::Saml::Request.new(params,session).redirect_url
305
+ end
306
+ ```
307
+ The params variable should contain the GET parameters of the request. The session variable should be the actual client session.
308
+
309
+ Based on your application requirements the consume action might look like this:
310
+ ```ruby
311
+ def consume
312
+ # Process the response and extract information
313
+ saml_response = Maestrano::Saml::Response.new(params[:SAMLResponse])
314
+ user_hash = Maestrano::SSO::BaseUser.new(saml_response).to_hash
315
+ group_hash = Maestrano::SSO::BaseGroup.new(saml_response).to_hash
316
+ membership_hash = Maestrano::SSO::BaseMembership.new(saml_response).to_hash
317
+
318
+ # Find or create the user and the organization
319
+ user = User.find_or_create_for_maestrano(user_hash)
320
+ organization = Organization.find_or_create_for_maestrano(group_hash)
321
+
322
+ # Add user to the organization if not there already
323
+ # Methods below should be coming from your application
324
+ unless organization.has_member?(user)
325
+ organization.add_member(user, role: membership_hash[:role])
326
+ end
327
+
328
+ # Set the Maestrano session (ultimately used for single logout)
329
+ Maestrano::SSO.set_session(session, user_hash)
330
+
331
+ # Sign the user in and redirect to application root
332
+ # To be customised depending on how you handle user
333
+ # sign in and
334
+ sign_in(user)
335
+ redirect_to root_path
336
+ end
337
+ ```
338
+ Note that for the consume action you should disable CSRF authenticity if your framework is using it by default. If CSRF authenticity is enabled then your app will complain on the fact that it is receiving a form without CSRF token.
339
+
340
+ ### Other Controllers
341
+ If you want your users to benefit from single logout then you should define the following filter in a module and include it in all your controllers except the one handling single sign-on authentication.
342
+
343
+ ```ruby
344
+ def verify_maestrano_session
345
+ if Maestrano.param(:sso_enabled)
346
+ if session && session[:maestrano] && !Maestrano::SSO::Session.new(session).valid?
347
+ redirect_to Maestrano::SSO.init_url
348
+ end
349
+ end
350
+ true
351
+ end
352
+ ```
353
+
354
+ ## Account Webhooks
355
+ Single sign on has been setup into your app and Maestrano users are now able to use your service. Great! Wait what happens when a business (group) decides to stop using your service? Also what happens when a user gets removed from a business? Well the controllers describes in this section are for Maestrano to be able to notify you of such events.
356
+
357
+ ### Groups Controller (service cancellation)
358
+ Sad as it is a business might decide to stop using your service at some point. On Maestrano billing entities are represented by groups (used for collaboration & billing). So when a business decides to stop using your service we will issue a DELETE request to the webhook.account.groups_path endpoint (typically /maestrano/account/groups/:id).
359
+
360
+ Maestrano only uses this controller for service cancellation so there is no need to implement any other type of action - ie: GET, PUT/PATCH or POST. The use of other http verbs might come in the future to improve the communication between Maestrano and your service but as of now it is not required.
361
+
362
+ The controller example below reimplements the authenticate_maestrano! method seen in the [metadata section](#metadata) for completeness. Utimately you should move this method to a helper if you can.
363
+
364
+ The example below is for Rails and need to be adapted depending on the framework you're using:
365
+ ```ruby
366
+ class MaestranoAccountGroupsController < ApplicationController
367
+ before_filter :authenticate_maestrano!
368
+
369
+ # DELETE /maestrano/account/groups/cld-1
370
+ # Delete an entire group
371
+ def destroy
372
+ group_uid = params[:id]
373
+
374
+ # Perform deletion steps here
375
+ # --
376
+ # If you need to perform a final checkout
377
+ # then you can call Maestrano::Account::Bill.create({.. final checkout details ..})
378
+ # --
379
+ # If Maestrano.param('sso.creation_mode') is set to virtual
380
+ # then you might want to delete/cancel/block all users under
381
+ # that group
382
+ # --
383
+ # E.g:
384
+ # organization = Organization.find_by_provider_and_uid('maestrano',group_uid)
385
+ #
386
+ # amount_cents = organization.calculate_total_due_remaining
387
+ # Maestrano::Account::Bill.create({
388
+ # group_id: group_uid,
389
+ # price_cents: amount_cents,
390
+ # description: "Final Payout"
391
+ # })
392
+ #
393
+ # if Maestrano.param('sso.creation_mode') == 'virtual'
394
+ # organization.members.where(provider:'maestrano').each do |user|
395
+ # user.destroy
396
+ # end
397
+ #
398
+ # organization.destroy
399
+ # render json: {success: true}, status: :success
400
+ #
401
+ end
402
+
403
+ private
404
+ def authenticate_maestrano!
405
+ authorized = false
406
+ authenticate_with_http_basic do |app_id, api_token|
407
+ authorized = Maestrano.authenticate(app_id,api_token)
408
+ end
409
+ unless authorized
410
+ render json: {error: 'Invalid credentials' }, status: :unauthorized
411
+ end
412
+ return true
413
+ end
414
+ end
415
+ ```
416
+
417
+ ### Group Users Controller (business member removal)
418
+ A business might decide at some point to revoke access to your services for one of its member. In such case we will issue a DELETE request to the webhook.account.group_users_path endpoint (typically /maestrano/account/groups/:group_id/users/:id).
419
+
420
+ Maestrano only uses this controller for user membership cancellation so there is no need to implement any other type of action - ie: GET, PUT/PATCH or POST. The use of other http verbs might come in the future to improve the communication between Maestrano and your service but as of now it is not required.
421
+
422
+ The controller example below reimplements the authenticate_maestrano! method seen in the [metadata section](#metadata) for completeness. Utimately you should move this method to a helper if you can.
423
+
424
+ The example below is for Rails and need to be adapted depending on the framework you're using:
425
+ ```ruby
426
+ class MaestranoAccountGroupUsersController < ApplicationController
427
+ before_filter :authenticate_maestrano!
428
+
429
+ # DELETE /maestrano/account/groups/cld-1
430
+ # Delete an entire group
431
+ def destroy
432
+ # Set the right uid based on Maestrano.param('sso.creation_mode')
433
+ user_uid = Maestrano.mask_user(params[:id],params[:group_id])
434
+ group_uid = params[:group_id]
435
+
436
+ # Perform association deletion steps here
437
+ # --
438
+ # If Maestrano.param('sso.creation_mode') is set to virtual
439
+ # then you might want to just delete/cancel/block the user
440
+ #
441
+ # E.g
442
+ # user = User.find_by_provider_and_uid('maestrano',user_uid)
443
+ # organization = Organization.find_by_provider_and_uid('maestrano',group_uid)
444
+ #
445
+ # if Maestrano.param('sso.creation_mode') == 'virtual'
446
+ # user.destroy
447
+ # else
448
+ # organization.remove_user(user)
449
+ # user.block_access! if user.reload.organizations.empty?
450
+ # end
451
+ #
452
+ # render json: {success: true}, status: :success
453
+ end
454
+
455
+ private
456
+ def authenticate_maestrano!
457
+ authorized = false
458
+ authenticate_with_http_basic do |app_id, api_token|
459
+ authorized = Maestrano.authenticate(app_id,api_token)
460
+ end
461
+ unless authorized
462
+ render json: {error: 'Invalid credentials' }, status: :unauthorized
463
+ end
464
+ return true
465
+ end
466
+ end
467
+ ```
468
+
469
+ ## API
470
+ The maestrano gem also provides bindings to its REST API allowing you to access, create, update or delete various entities under your account (e.g: billing).
471
+
472
+ ### Payment API
473
+
474
+ #### Bill
475
+ A bill represents a single charge on a given group.
476
+
477
+ ```ruby
478
+ Maestrano::Account::Bill
479
+ ```
480
+
481
+ ##### Attributes
482
+
483
+ <table>
484
+ <tr>
485
+ <th>Field</th>
486
+ <th>Mode</th>
487
+ <th>Type</th>
488
+ <th>Required</th>
489
+ <th>Default</th>
490
+ <th>Description</th>
491
+ <tr>
492
+
493
+ <tr>
494
+ <td><b>id</b></td>
495
+ <td>readonly</td>
496
+ <td>string</td>
497
+ <td>-</td>
498
+ <td>-</td>
499
+ <td>The id of the bill</td>
500
+ <tr>
501
+
502
+ <tr>
503
+ <td><b>group_id</b></td>
504
+ <td>read/write</td>
505
+ <td>string</td>
506
+ <td><b>Yes</b></td>
507
+ <td>-</td>
508
+ <td>The id of the group you are charging</td>
509
+ <tr>
510
+
511
+ <tr>
512
+ <td><b>price_cents</b></td>
513
+ <td>read/write</td>
514
+ <td>Integer</td>
515
+ <td><b>Yes</b></td>
516
+ <td>-</td>
517
+ <td>The amount in cents to charge to the customer</td>
518
+ <tr>
519
+
520
+ <tr>
521
+ <td><b>description</b></td>
522
+ <td>read/write</td>
523
+ <td>String</td>
524
+ <td><b>Yes</b></td>
525
+ <td>-</td>
526
+ <td>A description of the product billed as it should appear on customer invoice</td>
527
+ <tr>
528
+
529
+ <tr>
530
+ <td><b>created_at</b></td>
531
+ <td>readonly</td>
532
+ <td>Time</td>
533
+ <td>-</td>
534
+ <td>-</td>
535
+ <td>When the bill was created</td>
536
+ <tr>
537
+
538
+ <tr>
539
+ <td><b>updated_at</b></td>
540
+ <td>readonly</td>
541
+ <td>Time</td>
542
+ <td>-</td>
543
+ <td>-</td>
544
+ <td>When the bill was last updated</td>
545
+ <tr>
546
+
547
+ <tr>
548
+ <td><b>status</b></td>
549
+ <td>readonly</td>
550
+ <td>String</td>
551
+ <td>-</td>
552
+ <td>-</td>
553
+ <td>Status of the bill. Either 'submitted', 'invoiced' or 'cancelled'.</td>
554
+ <tr>
555
+
556
+ <tr>
557
+ <td><b>currency</b></td>
558
+ <td>read/write</td>
559
+ <td>String</td>
560
+ <td>-</td>
561
+ <td>AUD</td>
562
+ <td>The currency of the amount charged in <a href="http://en.wikipedia.org/wiki/ISO_4217#Active_codes">ISO 4217 format</a> (3 letter code)</td>
563
+ <tr>
564
+
565
+ <tr>
566
+ <td><b>units</b></td>
567
+ <td>read/write</td>
568
+ <td>Decimal(10,2)</td>
569
+ <td>-</td>
570
+ <td>1.0</td>
571
+ <td>How many units are billed for the amount charged</td>
572
+ <tr>
573
+
574
+ <tr>
575
+ <td><b>period_started_at</b></td>
576
+ <td>read/write</td>
577
+ <td>Time</td>
578
+ <td>-</td>
579
+ <td>-</td>
580
+ <td>If the bill relates to a specific period then specifies when the period started. Both period_started_at and period_ended_at need to be filled in order to appear on customer invoice.</td>
581
+ <tr>
582
+
583
+ <tr>
584
+ <td><b>period_ended_at</b></td>
585
+ <td>read/write</td>
586
+ <td>Time</td>
587
+ <td>-</td>
588
+ <td>-</td>
589
+ <td>If the bill relates to a specific period then specifies when the period ended. Both period_started_at and period_ended_at need to be filled in order to appear on customer invoice.</td>
590
+ <tr>
591
+
592
+ </table>
593
+
594
+ ##### Actions
595
+
596
+ List all bills you have created and iterate through the list
597
+ ```ruby
598
+ bills = Maestrano::Account::Bill.all
599
+ bills.each { |b| puts b.id }
600
+ ```
601
+
602
+ Access a single bill by id
603
+ ```ruby
604
+ bill = Maestrano::Account::Bill.retrieve("bill-f1d2s54")
605
+ puts bill.group_id
606
+ ```
607
+
608
+ Create a new bill
609
+ ```ruby
610
+ bill = Maestrano::Account::Bill.create(group_id: "cld-3", price_cents: 2000, description: "Product purchase")
611
+ puts bill.id
612
+ ```
613
+
614
+ Cancel a bill
615
+ ```ruby
616
+ bill = Maestrano::Account::Bill.retrieve("bill-f1d2s54")
617
+ bill.cancel
618
+ ```
619
+
620
+ #### Recurring Bill
621
+ A recurring bill charges a given customer at a regular interval without you having to do anything.
622
+
623
+ ```ruby
624
+ Maestrano::Account::RecurringBill
625
+ ```
626
+
627
+ ##### Attributes
628
+
629
+ <table>
630
+ <tr>
631
+ <th>Field</th>
632
+ <th>Mode</th>
633
+ <th>Type</th>
634
+ <th>Required</th>
635
+ <th>Default</th>
636
+ <th>Description</th>
637
+ <tr>
638
+
639
+ <tr>
640
+ <td><b>id</b></td>
641
+ <td>readonly</td>
642
+ <td>string</td>
643
+ <td>-</td>
644
+ <td>-</td>
645
+ <td>The id of the recurring bill</td>
646
+ <tr>
647
+
648
+ <tr>
649
+ <td><b>group_id</b></td>
650
+ <td>read/write</td>
651
+ <td>string</td>
652
+ <td><b>Yes</b></td>
653
+ <td>-</td>
654
+ <td>The id of the group you are charging</td>
655
+ <tr>
656
+
657
+ <tr>
658
+ <td><b>price_cents</b></td>
659
+ <td>read/write</td>
660
+ <td>Integer</td>
661
+ <td><b>Yes</b></td>
662
+ <td>-</td>
663
+ <td>The amount in cents to charge to the customer</td>
664
+ <tr>
665
+
666
+ <tr>
667
+ <td><b>description</b></td>
668
+ <td>read/write</td>
669
+ <td>String</td>
670
+ <td><b>Yes</b></td>
671
+ <td>-</td>
672
+ <td>A description of the product billed as it should appear on customer invoice</td>
673
+ <tr>
674
+
675
+ <tr>
676
+ <td><b>period</b></td>
677
+ <td>read/write</td>
678
+ <td>String</td>
679
+ <td>-</td>
680
+ <td>Month</td>
681
+ <td>The unit of measure for the billing cycle. Must be one of the following: 'Day', 'Week', 'SemiMonth', 'Month', 'Year'</td>
682
+ <tr>
683
+
684
+ <tr>
685
+ <td><b>frequency</b></td>
686
+ <td>read/write</td>
687
+ <td>Integer</td>
688
+ <td>-</td>
689
+ <td>1</td>
690
+ <td>The number of billing periods that make up one billing cycle. The combination of billing frequency and billing period must be less than or equal to one year. If the billing period is SemiMonth, the billing frequency must be 1.</td>
691
+ <tr>
692
+
693
+ <tr>
694
+ <td><b>cycles</b></td>
695
+ <td>read/write</td>
696
+ <td>Integer</td>
697
+ <td>-</td>
698
+ <td>nil</td>
699
+ <td>The number of cycles this bill should be active for. In other words it's the number of times this recurring bill should charge the customer.</td>
700
+ <tr>
701
+
702
+ <tr>
703
+ <td><b>start_date</b></td>
704
+ <td>read/write</td>
705
+ <td>Time</td>
706
+ <td>-</td>
707
+ <td>Now</td>
708
+ <td>The date when this recurring bill should start billing the customer</td>
709
+ <tr>
710
+
711
+ <tr>
712
+ <td><b>created_at</b></td>
713
+ <td>readonly</td>
714
+ <td>Time</td>
715
+ <td>-</td>
716
+ <td>-</td>
717
+ <td>When the recurring bill was created</td>
718
+ <tr>
719
+
720
+ <tr>
721
+ <td><b>updated_at</b></td>
722
+ <td>readonly</td>
723
+ <td>Time</td>
724
+ <td>-</td>
725
+ <td>-</td>
726
+ <td>When the recurring bill was last updated</td>
727
+ <tr>
728
+
729
+ <tr>
730
+ <td><b>currency</b></td>
731
+ <td>read/write</td>
732
+ <td>String</td>
733
+ <td>-</td>
734
+ <td>AUD</td>
735
+ <td>The currency of the amount charged in <a href="http://en.wikipedia.org/wiki/ISO_4217#Active_codes">ISO 4217 format</a> (3 letter code)</td>
736
+ <tr>
737
+
738
+ <tr>
739
+ <td><b>status</b></td>
740
+ <td>readonly</td>
741
+ <td>String</td>
742
+ <td>-</td>
743
+ <td>-</td>
744
+ <td>Status of the recurring bill. Either 'submitted', 'active', 'expired' or 'cancelled'.</td>
745
+ <tr>
746
+
747
+ <tr>
748
+ <td><b>initial_cents</b></td>
749
+ <td>read/write</td>
750
+ <td>Integer</td>
751
+ <td><b>-</b></td>
752
+ <td>0</td>
753
+ <td>Initial non-recurring payment amount - in cents - due immediately upon creating the recurring bill</td>
754
+ <tr>
755
+
756
+ </table>
757
+
758
+ ##### Actions
759
+
760
+ List all recurring bills you have created and iterate through the list
761
+ ```ruby
762
+ rec_bills = Maestrano::Account::RecurringBill.all
763
+ rec_bills.each { |b| puts b.id }
764
+ ```
765
+
766
+ Access a single recurring bill by id
767
+ ```ruby
768
+ rec_bill = Maestrano::Account::RecurringBill.retrieve("rbill-f1d2s54")
769
+ puts rec_bill.group_id
770
+ ```
771
+
772
+ Create a new recurring bill
773
+ ```ruby
774
+ rec_bill = Maestrano::Account::RecurringBill.create(group_id: "cld-3", price_cents: 2000, description: "Product purchase", period: 'Month', start_date: Time.now)
775
+ puts rec_bill.id
776
+ ```
777
+
778
+ Cancel a recurring bill
779
+ ```ruby
780
+ rec_bill = Maestrano::Account::RecurringBill.retrieve("rbill-f1d2s54")
781
+ rec_bill.cancel
782
+ ```
783
+
784
+
785
+ ## Support
786
+ This README is still in the process of being written and improved. As such it might not cover some of the questions you might have.
787
+
788
+ So if you have any question or need help integrating with us just let us know at support@maestrano.com
789
+
790
+ ## License
791
+
792
+ MIT License. Copyright 2014 Maestrano Pty Ltd. https://maestrano.com
793
+
794
+ You are not granted rights or licenses to the trademarks of Maestrano.