maestrano 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- maestrano (0.1.0)
4
+ maestrano (0.2.0)
5
5
  json (~> 1.8)
6
6
  mime-types (~> 1.25)
7
7
  nokogiri (>= 1.5.0)
@@ -36,8 +36,8 @@ PLATFORMS
36
36
 
37
37
  DEPENDENCIES
38
38
  maestrano!
39
- mocha (~> 0.13.2)
40
- rake
41
- shoulda (~> 2.11.0)
42
- test-unit
39
+ mocha (~> 0.13)
40
+ rake (~> 10)
41
+ shoulda (~> 2.11)
42
+ test-unit (~> 2)
43
43
  timecop (<= 0.6.0)
data/README.md CHANGED
@@ -1,4 +1,246 @@
1
- maestrano-ruby
2
- ==============
1
+ <p align="center">
2
+ <img src="https://raw.github.com/maestrano/maestrano-rails/master/maestrano.png" alt="Maestrano Logo">
3
+ </p>
3
4
 
4
- Maestrano Ruby Bindings
5
+ Maestrano Cloud Integration is currently in closed beta. Want to know more? Send us an email to <contact@maestrano.com>.
6
+
7
+ ## Getting Setup
8
+ Before integrating with us you will need an API Key. Maestrano Cloud Integration being still in closed beta you will need to contact us beforehand to gain production access.
9
+
10
+ For testing purpose we provide an API Sandbox where you can freely obtain an API Token. The sandbox is great to test single sign-on and API integration (e.g: billing API).
11
+
12
+ To get started just go to: http://api-sandbox.maestrano.io
13
+
14
+ ## Getting Started with Rails
15
+
16
+ If you're looking at integrating Maestrano in your Rails application then you should use the maestrano-rails gem.
17
+
18
+ More details on the [maestrano-rails project page](https://github.com/maestrano/maestrano-rails).
19
+
20
+ ## Getting Started
21
+ The first step is to create an initializer to configure the behaviour of the Maestrano gem - including setting your API key.
22
+
23
+ The initializer should look like this:
24
+ ```ruby
25
+ # Use this block to configure the behaviour of Maestrano
26
+ # in your app
27
+ Maestrano.configure do |config|
28
+
29
+ # ==> Environment configuration
30
+ # The environment to connect to.
31
+ # If set to 'production' then all Single Sign-On (SSO) and API requests
32
+ # will be made to maestrano.com
33
+ # If set to 'test' then requests will be made to api-sandbox.maestrano.io
34
+ # The api-sandbox allows you to easily test integration scenarios.
35
+ # More details on http://api-sandbox.maestrano.io
36
+ config.environment = 'test' # or 'production'
37
+
38
+ # ==> API key
39
+ # Your application API key which you can retrieve on http://maestrano.com
40
+ # via your cloud partner dashboard.
41
+ # For testing you can retrieve/generate an api_key from the API Sandbox directly
42
+ # on http://api-sandbox.maestrano.io
43
+ config.api_key = (config.environment == 'production' ? 'prod_api_key' : 'sandbox_api_key')
44
+
45
+ # ==> Single Sign-On activation
46
+ # Enable/Disable single sign-on. When troubleshooting authentication issues
47
+ # you might want to disable SSO temporarily
48
+ config.sso_enabled = true
49
+
50
+ # ==> Application host
51
+ # This is your application host (e.g: mysuperapp.com) which is ultimately
52
+ # used to redirect users to the right SAML url during SSO handshake.
53
+ config.app_host = (config.environment == 'production' ? 'https://my-production-app.com' : 'http://localhost:3000')
54
+
55
+ # ==> SSO Initialization endpoint
56
+ # This is your application path to the SAML endpoint that allows users to
57
+ # initialize SSO authentication. Upon reaching this endpoint users your
58
+ # application will automatically create a SAML request and redirect the user
59
+ # to Maestrano. Maestrano will then authenticate and authorize the user. Upon
60
+ # authorization the user gets redirected to your application consumer endpoint
61
+ # (see below) for initial setup and/or login.
62
+ # The controller for this path is automatically
63
+ # generated when you run 'rake maestrano:install' and is available at
64
+ # <rails_root>/app/controllers/maestrano/auth/saml.rb
65
+ config.sso_app_init_path = '/maestrano/auth/saml/init'
66
+
67
+ # ==> SSO Consumer endpoint
68
+ # This is your application path to the SAML endpoint that allows users to
69
+ # finalize SSO authentication. During the 'consume' action your application
70
+ # sets users (and associated group) up and/or log them in.
71
+ # The controller for this path is automatically
72
+ # generated when you run 'rake maestrano:install' and is available at
73
+ # <rails_root>/app/controllers/maestrano/auth/saml.rb
74
+ config.sso_app_consume_path = '/maestrano/auth/saml/consume'
75
+
76
+ # ==> SSO User creation mode
77
+ # !IMPORTANT
78
+ # On Maestrano users can take several "instances" of your service. You can consider
79
+ # each "instance" as 1) a billing entity and 2) a collaboration group (this is
80
+ # equivalent to a 'customer account' in a commercial world). When users login to
81
+ # your application via single sign-on they actually login via a specific group which
82
+ # is then supposed to determine which data they have access to inside your application.
83
+ #
84
+ # E.g: John and Jack are part of group 1. They should see the same data when they login to
85
+ # your application (employee info, analytics, sales etc..). John is also part of group 2
86
+ # but not Jack. Therefore only John should be able to see the data belonging to group 2.
87
+ #
88
+ # In most application this is done via collaboration/sharing/permission groups which is
89
+ # why a group is required to be created when a new user logs in via a new group (and
90
+ # also for billing purpose - you charge a group, not a user directly).
91
+ #
92
+ # == mode: 'real'
93
+ # In an ideal world a user should be able to belong to several groups in your application.
94
+ # In this case you would set the 'user_creation_mode' to 'real' which means that the uid
95
+ # and email we pass to you are the actual user email and maestrano universal id.
96
+ #
97
+ # == mode: 'virtual'
98
+ # Now let's say that due to technical constraint your application cannot authorize a user
99
+ # to belong to several groups. Well next time John logs in via a different group there will
100
+ # be a problem: the user already exists (based on uid or email) and cannot be assigned
101
+ # to a second group. To fix this you can set the 'user_creation_mode' to 'virtual'. In this
102
+ # mode users get assigned a truly unique uid and email across groups. So next time John logs
103
+ # in a whole new user account can be created for him without any validation problem. In this
104
+ # mode the email we assign to him looks like "usr-sdf54.cld-45aa2@mail.maestrano.com". But don't
105
+ # worry we take care of forwarding any email you would send to this address
106
+ #
107
+ config.user_creation_mode = 'virtual' # or 'real'
108
+ end
109
+ ```
110
+
111
+ ## Single Sign-On Setup
112
+ 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.
113
+
114
+ 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)
115
+
116
+ ### User Setup
117
+ 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..
118
+
119
+ Assuming the above the method could look like this:
120
+ ```ruby
121
+ # Only if you need to set a random password
122
+ require 'digest/sha1'
123
+
124
+ class User
125
+
126
+ ...
127
+
128
+ def self.find_or_create_for_maestrano(sso_hash)
129
+ user = self.where(provider:'maestrano', uid: sso_hash[:uid]).first
130
+
131
+ unless user
132
+ user = self.new
133
+
134
+ # Mapping
135
+ user.provider = 'maestrano'
136
+ user.uid = sso_hash[:uid]
137
+ user.name = sso_hash[:info][:first_name]
138
+ user.surname = sso_hash[:info][:last_name]
139
+ user.email = sso_hash[:info][:email]
140
+ # user.country_alpha2 = sso_hash[:info][:country]
141
+ # user.company = sso_hash[:info][:company_name]
142
+ # user.password = Digest::SHA1.hexdigest("#{Time.now}-#{rand(100)}")[0..20]
143
+ # user.password_confirmation = user.password
144
+ # user.some_other_required_field = 'some-appropriate-default-value'
145
+
146
+ # Save the user
147
+ user.save
148
+ end
149
+
150
+ return user
151
+ end
152
+
153
+ ...
154
+
155
+ end
156
+ ```
157
+
158
+ ### Group Setup
159
+ 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)
160
+
161
+ Assuming a group model called 'Organization', the find_or_create_for_maestrano class method could look like this:
162
+ ```ruby
163
+ class Organization
164
+
165
+ ...
166
+
167
+ def self.find_or_create_for_maestrano(sso_hash)
168
+ organization = self.where(provider:'maestrano', uid: sso_hash[:uid]).first
169
+
170
+ unless organization
171
+ organization = self.new
172
+
173
+ # Mapping
174
+ organization.provider = 'maestrano'
175
+ organization.uid = sso_hash[:uid]
176
+ organization.name = sso_hash[:info][:company_name] || 'Some default'
177
+ # organization.country_alpha2 = sso_hash[:info][:country]
178
+ # organization.free_trial_end_at = sso_hash[:info][:free_trial_end_at]
179
+
180
+ # Save the organization
181
+ organization.save
182
+ end
183
+
184
+ return organization
185
+ end
186
+
187
+ ...
188
+
189
+ end
190
+ ```
191
+
192
+ ### Controller Setup
193
+ 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.
194
+
195
+ The init action is all handled via Maestrano methods and should look like this:
196
+ ```ruby
197
+ def init
198
+ redirect_to Maestrano::Saml::Request.new(params,session).redirect_url
199
+ end
200
+ ```
201
+ The params variable should contain the GET parameters of the request. The session variable should be the actual client session.
202
+
203
+ Based on your application requirements the consume action might look like this:
204
+ ```ruby
205
+ def consume
206
+ # Process the response and extract information
207
+ saml_response = Maestrano::Saml::Response.new(params[:SAMLResponse])
208
+ user_hash = Maestrano::SSO::BaseUser.new(saml_response).to_hash
209
+ group_hash = Maestrano::SSO::BaseGroup.new(saml_response).to_hash
210
+ membership_hash = Maestrano::SSO::BaseMembership.new(saml_response).to_hash
211
+
212
+ # Find or create the user and the organization
213
+ user = User.find_or_create_for_maestrano(user_hash)
214
+ organization = Organization.find_or_create_for_maestrano(group_hash)
215
+
216
+ # Add user to the organization if not there already
217
+ # Methods below should be coming from your application
218
+ unless organization.has_member?(user)
219
+ organization.add_member(user, role: membership_hash[:role])
220
+ end
221
+
222
+ # Set the Maestrano session (ultimately used for single logout)
223
+ Maestrano::SSO.set_session(session, user_hash)
224
+
225
+ # Sign the user in and redirect to application root
226
+ # To be customised depending on how you handle user
227
+ # sign in and
228
+ sign_in(user)
229
+ redirect_to root_path
230
+ end
231
+ ```
232
+ 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.
233
+
234
+ ### Other Controllers
235
+ 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.
236
+
237
+ ```ruby
238
+ def verify_maestrano_session
239
+ if Maestrano.param(:sso_enabled)
240
+ if session && session[:mno_uid] && !Maestrano::SSO::Session.new(session).valid?
241
+ redirect_to Maestrano::SSO.init_url
242
+ end
243
+ end
244
+ true
245
+ end
246
+ ```
data/lib/maestrano.rb CHANGED
@@ -20,6 +20,7 @@ require 'maestrano/saml/metadata'
20
20
  require 'maestrano/sso'
21
21
  require 'maestrano/sso/base_user'
22
22
  require 'maestrano/sso/base_group'
23
+ require 'maestrano/sso/base_membership'
23
24
  require 'maestrano/sso/session'
24
25
  require 'maestrano/sso/user'
25
26
  require 'maestrano/sso/group'
@@ -45,6 +46,7 @@ require 'maestrano/api/resource'
45
46
 
46
47
  # API - Account Entities
47
48
  require 'maestrano/account/bill'
49
+ require 'maestrano/account/recurring_bill'
48
50
 
49
51
  module Maestrano
50
52
 
@@ -0,0 +1,14 @@
1
+ module Maestrano
2
+ module Account
3
+ class RecurringBill < Maestrano::API::Resource
4
+ include Maestrano::API::Operation::List
5
+ include Maestrano::API::Operation::Create
6
+
7
+ def cancel
8
+ response, api_key = Maestrano::API::Operation::Base.request(:delete, url, @api_key)
9
+ refresh_from(response, api_key)
10
+ self
11
+ end
12
+ end
13
+ end
14
+ end
@@ -10,9 +10,9 @@ module Maestrano
10
10
  raise NotImplementedError.new('Maestrano::API::Resource is an abstract class. You should perform actions on its subclasses (Bill, Customer, etc.)')
11
11
  end
12
12
  if class_name.is_a?(Array)
13
- class_name.map { |w| CGI.escape(w.downcase) }.join("/") + 's'
13
+ class_name.map { |w| CGI.escape(self.underscore(w)) }.join("/") + 's'
14
14
  else
15
- "#{CGI.escape(class_name.downcase)}s"
15
+ "#{CGI.escape(self.underscore(class_name))}s"
16
16
  end
17
17
  end
18
18
 
@@ -34,6 +34,14 @@ module Maestrano
34
34
  instance.refresh
35
35
  instance
36
36
  end
37
+
38
+ def self.underscore(string_val)
39
+ string_val.gsub(/::/, '/').
40
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
41
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
42
+ tr("-", "_").
43
+ downcase
44
+ end
37
45
  end
38
46
  end
39
47
  end
@@ -20,8 +20,9 @@ module Maestrano
20
20
 
21
21
  def self.object_classes
22
22
  @object_classes ||= {
23
- 'account_bill' => Maestrano::Account::Bill,
24
- 'internal_list_object' => Maestrano::API::ListObject
23
+ 'account_bill' => Maestrano::Account::Bill,
24
+ 'account_recurring_bill' => Maestrano::Account::RecurringBill,
25
+ 'internal_list_object' => Maestrano::API::ListObject
25
26
  }
26
27
  end
27
28
 
@@ -0,0 +1,25 @@
1
+ module Maestrano
2
+ module SSO
3
+ class BaseMembership
4
+ attr_reader :user_uid,:group_uid,:role
5
+
6
+ # Initializer
7
+ # @param Maestrano::SAML::Response
8
+ def initialize(saml_response)
9
+ att = saml_response.attributes
10
+ @user_uid = att['uid']
11
+ @group_uid = att['group_uid']
12
+ @role = att['group_role']
13
+ end
14
+
15
+ def to_hash
16
+ {
17
+ provider: 'maestrano',
18
+ group_uid: self.group_uid,
19
+ user_uid: self.user_uid,
20
+ role: self.role
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module Maestrano
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/maestrano.png ADDED
Binary file
@@ -50,6 +50,39 @@ module APITestHelper
50
50
  }
51
51
  end
52
52
 
53
+ def test_account_recurring_bill_content(params={})
54
+ {
55
+ object: 'account_recurring_bill',
56
+ id: 'rbill-1',
57
+ group_id: 'cld-1',
58
+ created_at: Time.now.utc.iso8601,
59
+ price_cents: 2300,
60
+ status: 'submitted',
61
+ currency: 'AUD',
62
+ description: 'Bill for something',
63
+ start_date: Time.now.utc.iso8601,
64
+ period: 'Month',
65
+ frequency: 1,
66
+ cycles: nil
67
+ }.merge(params)
68
+ end
69
+
70
+ def test_account_recurring_bill(params={})
71
+ {
72
+ success: true,
73
+ errors: {},
74
+ data: test_account_recurring_bill_content(params)
75
+ }
76
+ end
77
+
78
+ def test_account_recurring_bill_array
79
+ {
80
+ success: true,
81
+ errors: {},
82
+ data: [test_account_recurring_bill_content, test_account_recurring_bill_content, test_account_recurring_bill_content],
83
+ }
84
+ end
85
+
53
86
  def test_invalid_api_key_error
54
87
  {
55
88
  'success' => false,
@@ -5,7 +5,7 @@ module Maestrano
5
5
  class BillTest < Test::Unit::TestCase
6
6
  include APITestHelper
7
7
 
8
- should "bills should be listable" do
8
+ should "should be listable" do
9
9
  @api_mock.expects(:get).once.returns(test_response(test_account_bill_array))
10
10
  c = Maestrano::Account::Bill.all
11
11
  assert c.data.kind_of? Array
@@ -14,13 +14,13 @@ module Maestrano
14
14
  end
15
15
  end
16
16
 
17
- should "bills should be cancellable" do
17
+ should "should be cancellable" do
18
18
  @api_mock.expects(:delete).once.returns(test_response(test_account_bill))
19
19
  c = Maestrano::Account::Bill.construct_from(test_account_bill[:data])
20
20
  c.cancel
21
21
  end
22
22
 
23
- should "bills should not be updateable" do
23
+ should "should not be updateable" do
24
24
  assert_raises NoMethodError do
25
25
  @api_mock.stubs(:put).returns(test_response(test_account_bill))
26
26
  c = Maestrano::Account::Bill.construct_from(test_account_bill[:data])
@@ -29,7 +29,7 @@ module Maestrano
29
29
  end
30
30
 
31
31
 
32
- should "create should successfully create a remote bill when passed correct parameters" do
32
+ should "should successfully create a remote bill when passed correct parameters" do
33
33
  @api_mock.expects(:post).with do |url, api_key, params|
34
34
  url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/bills" && api_key.nil? &&
35
35
  CGI.parse(params) == {"group_id"=>["cld-1"], "price_cents"=>["23000"], "currency"=>["AUD"], "description"=>["Some bill"]}
@@ -0,0 +1,49 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module Maestrano
4
+ module Account
5
+ class RecurringBillTest < Test::Unit::TestCase
6
+ include APITestHelper
7
+
8
+ should "be listable" do
9
+ @api_mock.expects(:get).once.returns(test_response(test_account_recurring_bill_array))
10
+ c = Maestrano::Account::RecurringBill.all
11
+ assert c.data.kind_of? Array
12
+ c.each do |bill|
13
+ assert bill.kind_of?(Maestrano::Account::RecurringBill)
14
+ end
15
+ end
16
+
17
+ should "be cancellable" do
18
+ @api_mock.expects(:delete).once.returns(test_response(test_account_recurring_bill))
19
+ c = Maestrano::Account::RecurringBill.construct_from(test_account_recurring_bill[:data])
20
+ c.cancel
21
+ end
22
+
23
+ should "not be updateable" do
24
+ assert_raises NoMethodError do
25
+ @api_mock.stubs(:put).returns(test_response(test_account_recurring_bill))
26
+ c = Maestrano::Account::RecurringBill.construct_from(test_account_recurring_bill[:data])
27
+ c.save
28
+ end
29
+ end
30
+
31
+
32
+ should "successfully create a remote bill when passed correct parameters" do
33
+ @api_mock.expects(:post).with do |url, api_key, params|
34
+ url == "#{Maestrano.param('api_host')}#{Maestrano.param('api_base')}account/recurring_bills" && api_key.nil? &&
35
+ CGI.parse(params) == {"group_id"=>["cld-1"], "price_cents"=>["23000"], "currency"=>["AUD"], "description"=>["Some recurring bill"], "period"=>["Month"]}
36
+ end.once.returns(test_response(test_account_recurring_bill))
37
+
38
+ recurring_bill = Maestrano::Account::RecurringBill.create({
39
+ group_id: 'cld-1',
40
+ price_cents: 23000,
41
+ currency: 'AUD',
42
+ description: 'Some recurring bill',
43
+ period: 'Month'
44
+ })
45
+ assert recurring_bill.id
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module Maestrano
4
+ module SSO
5
+ class BaseMembershipTest < Test::Unit::TestCase
6
+ include SamlTestHelper
7
+
8
+ setup do
9
+ @saml_response = Maestrano::Saml::Response.new(response_document)
10
+ @saml_response.stubs(:attributes).returns({
11
+ 'mno_session' => 'f54sd54fd64fs5df4s3d48gf2',
12
+ 'mno_session_recheck' => Time.now.utc.iso8601,
13
+ 'group_uid' => 'cld-1',
14
+ 'group_end_free_trial' => Time.now.utc.iso8601,
15
+ 'group_role' => 'Admin',
16
+ 'uid' => "usr-1",
17
+ 'virtual_uid' => "usr-1.cld-1",
18
+ 'email' => "j.doe@doecorp.com",
19
+ 'virtual_email' => "usr-1.cld-1@mail.maestrano.com",
20
+ 'name' => "John",
21
+ "surname" => "Doe",
22
+ "country" => "AU",
23
+ "company_name" => "DoeCorp"
24
+ })
25
+ end
26
+
27
+ should "extract the rights attributes from the saml response" do
28
+ membership = Maestrano::SSO::BaseMembership.new(@saml_response)
29
+ assert membership.group_uid == @saml_response.attributes['group_uid']
30
+ assert membership.user_uid == @saml_response.attributes['uid']
31
+ assert membership.role == @saml_response.attributes['group_role']
32
+ end
33
+
34
+ should "have the right hash representation" do
35
+ membership = Maestrano::SSO::BaseMembership.new(@saml_response)
36
+ assert membership.to_hash == {
37
+ provider: 'maestrano',
38
+ group_uid: membership.group_uid,
39
+ user_uid: membership.user_uid,
40
+ role: membership.role,
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -40,7 +40,7 @@ module Maestrano
40
40
  should "return the right saml_settings" do
41
41
  settings = Maestrano::SSO.saml_settings
42
42
  assert settings.assertion_consumer_service_url == Maestrano::SSO.consume_url
43
- assert settings.issuer == Maestrano.param('app_host')
43
+ assert settings.issuer == Maestrano.param('api_key')
44
44
  assert settings.idp_sso_target_url == Maestrano::SSO.idp_url
45
45
  assert settings.idp_cert_fingerprint == Maestrano.param('sso_x509_fingerprint')
46
46
  assert settings.name_identifier_format == Maestrano.param('sso_name_id_format')
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: maestrano
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.0
5
+ version: 0.3.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Arnaud Lachaume
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2014-05-28 00:00:00 Z
13
+ date: 2014-05-29 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rest-client
@@ -141,6 +141,7 @@ files:
141
141
  - bin/maestrano-console
142
142
  - lib/maestrano.rb
143
143
  - lib/maestrano/account/bill.rb
144
+ - lib/maestrano/account/recurring_bill.rb
144
145
  - lib/maestrano/api/error/authentication_error.rb
145
146
  - lib/maestrano/api/error/base_error.rb
146
147
  - lib/maestrano/api/error/connection_error.rb
@@ -166,6 +167,7 @@ files:
166
167
  - lib/maestrano/saml/validation_error.rb
167
168
  - lib/maestrano/sso.rb
168
169
  - lib/maestrano/sso/base_group.rb
170
+ - lib/maestrano/sso/base_membership.rb
169
171
  - lib/maestrano/sso/base_user.rb
170
172
  - lib/maestrano/sso/group.rb
171
173
  - lib/maestrano/sso/session.rb
@@ -173,9 +175,11 @@ files:
173
175
  - lib/maestrano/version.rb
174
176
  - lib/maestrano/xml_security/signed_document.rb
175
177
  - maestrano.gemspec
178
+ - maestrano.png
176
179
  - test/helpers/api_helpers.rb
177
180
  - test/helpers/saml_helpers.rb
178
181
  - test/maestrano/account/bill_test.rb
182
+ - test/maestrano/account/recurring_bill_test.rb
179
183
  - test/maestrano/api/list_object_test.rb
180
184
  - test/maestrano/api/object_test.rb
181
185
  - test/maestrano/api/resource_test.rb
@@ -185,6 +189,7 @@ files:
185
189
  - test/maestrano/saml/response_test.rb
186
190
  - test/maestrano/saml/settings_test.rb
187
191
  - test/maestrano/sso/base_group_test.rb
192
+ - test/maestrano/sso/base_membership_test.rb
188
193
  - test/maestrano/sso/base_user_test.rb
189
194
  - test/maestrano/sso/group_test.rb
190
195
  - test/maestrano/sso/session_test.rb
@@ -243,6 +248,7 @@ test_files:
243
248
  - test/helpers/api_helpers.rb
244
249
  - test/helpers/saml_helpers.rb
245
250
  - test/maestrano/account/bill_test.rb
251
+ - test/maestrano/account/recurring_bill_test.rb
246
252
  - test/maestrano/api/list_object_test.rb
247
253
  - test/maestrano/api/object_test.rb
248
254
  - test/maestrano/api/resource_test.rb
@@ -252,6 +258,7 @@ test_files:
252
258
  - test/maestrano/saml/response_test.rb
253
259
  - test/maestrano/saml/settings_test.rb
254
260
  - test/maestrano/sso/base_group_test.rb
261
+ - test/maestrano/sso/base_membership_test.rb
255
262
  - test/maestrano/sso/base_user_test.rb
256
263
  - test/maestrano/sso/group_test.rb
257
264
  - test/maestrano/sso/session_test.rb