maitre_d 0.4.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -1
  3. data/Gemfile +0 -5
  4. data/README.textile +24 -54
  5. data/addon-manifest.json +11 -4
  6. data/lib/maitre_d.rb +4 -1
  7. data/lib/maitre_d/api.rb +32 -0
  8. data/lib/maitre_d/api/authenticated.rb +30 -0
  9. data/lib/maitre_d/api/authentication_guard.rb +25 -0
  10. data/lib/maitre_d/api/change_plan.rb +15 -0
  11. data/lib/maitre_d/api/create.rb +7 -0
  12. data/lib/maitre_d/api/delete.rb +13 -0
  13. data/lib/maitre_d/api/sso.rb +45 -0
  14. data/lib/maitre_d/api/sso_guard.rb +31 -0
  15. data/lib/maitre_d/heroku.rb +4 -4
  16. data/maitre_d.gemspec +8 -7
  17. data/spec/api/heroku/provisioning_spec.rb +49 -30
  18. data/spec/api/heroku/single_sign_on_spec.rb +30 -20
  19. data/spec/internal/app/listeners/heroku_listener.rb +4 -4
  20. data/spec/internal/config/initializers/heroku.rb +2 -2
  21. data/spec/internal/config/routes.rb +2 -5
  22. data/spec/spec_helper.rb +1 -2
  23. metadata +57 -57
  24. data/lib/maitre_d/broadstack.rb +0 -40
  25. data/lib/maitre_d/broadstack/api.rb +0 -49
  26. data/lib/maitre_d/broadstack/api_helpers.rb +0 -44
  27. data/lib/maitre_d/cloud_control.rb +0 -40
  28. data/lib/maitre_d/cloud_control/api.rb +0 -47
  29. data/lib/maitre_d/cloud_control/api_helpers.rb +0 -28
  30. data/lib/maitre_d/heroku/api.rb +0 -47
  31. data/lib/maitre_d/heroku/api_helpers.rb +0 -46
  32. data/lib/maitre_d/heroku/listener.rb +0 -17
  33. data/lib/maitre_d/opperator.rb +0 -25
  34. data/lib/maitre_d/opperator/api.rb +0 -25
  35. data/lib/maitre_d/opperator/api_helpers.rb +0 -11
  36. data/lib/maitre_d/opperator/listener.rb +0 -13
  37. data/spec/api/broadstack/provisioning_spec.rb +0 -94
  38. data/spec/api/broadstack/single_sign_on_spec.rb +0 -49
  39. data/spec/api/cloud_control/provisioning_spec.rb +0 -94
  40. data/spec/api/cloud_control/single_sign_on_spec.rb +0 -49
  41. data/spec/api/opperator/provisioning_spec.rb +0 -55
  42. data/spec/internal/app/listeners/broadstack_listener.rb +0 -30
  43. data/spec/internal/app/listeners/cloud_control_listener.rb +0 -29
  44. data/spec/internal/app/listeners/opperator_listener.rb +0 -16
  45. data/spec/internal/config/initializers/broadstack.rb +0 -8
  46. data/spec/internal/config/initializers/cloud_control.rb +0 -8
  47. data/spec/internal/config/initializers/opperator.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f3c4e61ea06b3875e6b2a365f0acabad42b242a9
4
- data.tar.gz: 3767ddfb5f8b6e7d2f044ba6b978a4a9eb88b58e
2
+ SHA256:
3
+ metadata.gz: 5887c3acaa5919c45a510a84790bb775981f8671c217cdecb00e309ba77692d0
4
+ data.tar.gz: b4b5aff28f23c8f5c3babb222f625edd97e8dba7308397f97e7981931bef4d04
5
5
  SHA512:
6
- metadata.gz: e4c934c994c138437397a82f584a25e2631aaea6182b09a97d2a1952f1c0687894750d48e79b44b7ae863ce92cfb260de731cbd26d9d310d7f72bfee5a84e566
7
- data.tar.gz: 184847e1566082599f09efead691839955baca8f6100b0472b4c1a09b880d20089a9982c07a3614e34c6b445594d5ebacda971ba1424d14f2309c6305442fe2d
6
+ metadata.gz: a22aa34223c9931835013c46379aecc12181deee3e8127f6e24e43d7ac819e50aabdcff6f32692a07ccf1d1a3d6e6d5ce3854cd3f0c06aea8d18c08efdb47515
7
+ data.tar.gz: 4be65747aeaa43a1c34845e3c9f3acbe9fec96466f2510eb926f935b3f9d34bd475ad65cc5662f8919ede68af7a1615a9f3f42b49df43aee9cf0595555571a34
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
3
+ - 2.6.6
4
+ - 2.7.2
4
5
  script: bundle exec rspec spec
data/Gemfile CHANGED
@@ -1,8 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
-
5
- # Until a new Grape release is out:
6
- gem 'grape',
7
- :git => 'git://github.com/intridea/grape.git',
8
- :ref => '212c96cdfb253a59bc93790808c568e559d04468'
@@ -1,32 +1,26 @@
1
1
  h1. Maître d'
2
2
 
3
- "!https://secure.travis-ci.org/flying-sphinx/maitre_d.png!":http://travis-ci.org/flying-sphinx/maitre_d
3
+ "!https://secure.travis-ci.org/flying-sphinx/maitre_d.svg!":http://travis-ci.org/flying-sphinx/maitre_d
4
4
 
5
- Rack APIs powered by Grape for managing Heroku, CloudControl and Opperator add-ons.
5
+ Rack APIs powered by Sliver for managing Heroku add-ons.
6
6
 
7
7
  Maître d' manages all the authorisation checking for API requests and provides simple hooks for you to write just the code you need to handle provisioning, plan changes, deprovisioning and single-sign-on (SSO) requests.
8
8
 
9
9
  h2. Installing
10
10
 
11
- Add the following to your Gemfile - and take note that we need the git repository reference for Grape, as it includes some needed commits that aren't in the latest gem release.
11
+ Add the following to your Gemfile:
12
12
 
13
- <pre><code>gem 'grape',
14
- :git => 'git://github.com/intridea/grape.git',
15
- :ref => '212c96cdfb253a59bc93790808c568e559d04468'
16
- gem 'maitre_d', '~> 0.4.0'</code></pre>
13
+ <pre><code>gem 'maitre_d', '~> 0.7.1'</code></pre>
17
14
 
18
15
  h3. With Rails
19
16
 
20
- Add the appropriate Rack apps to your routes file:
17
+ Add the Rack app to your routes file:
21
18
 
22
- <pre><code>mount MaitreD::Broadstack::API => '/broadstack'
23
- mount MaitreD::Heroku::API => '/heroku'
24
- mount MaitreD::CloudControl::API => '/cloudcontrol'
25
- mount MaitreD::Opperator::API => '/opperator'</code></pre>
19
+ <pre><code>mount MaitreD::API.new(MaitreD::Heroku) => '/heroku'</code></pre>
26
20
 
27
21
  h3. Without Rails
28
22
 
29
- This library provides two APIs as mountable Rack applications for you - so you'll want to mount them at the appropriate paths: @MaitreD::Heroku::API@ at @/heroku@, @MaitreD::CloudControl::API@ at @/cloudcontrol@, and/or @MaitreD::Opperator::API@ at @/opperator@.
23
+ As shown above, you can use Maître d' Rack API, mounting them to wherever you see fit. Once upon a time Heroku expected its endpoints to be at /heroku, but now things are a little more flexible.
30
24
 
31
25
  h2. Configuration
32
26
 
@@ -39,44 +33,28 @@ MaitreD::Heroku.configure do |config|
39
33
  config.password = 'random'
40
34
  config.sso_salt = 'gibberish'
41
35
  config.listener = HerokuListener
42
- end
43
-
44
- require 'maitre_d/cloud_control'
45
-
46
- MaitreD::CloudControl.configure do |config|
47
- config.id = 'addon-id'
48
- config.password = 'random'
49
- config.sso_salt = 'gibberish'
50
- config.listener = CloudControlListener
51
- end
52
-
53
- require 'maitre_d/opperator'
54
-
55
- MaitreD::Opperator.configure do |config|
56
- config.shared_secret = 'something-special'
57
- config.listener = OpperatorListener
58
36
  end</code></pre>
59
37
 
60
- The listeners that are mentioned in the code above are classes, which will handle valid API requests. Read on for more details on how to set them up.
38
+ The listener that is mentioned in the code above is a class, which will handle valid API requests. Read on for more details on how to set them up.
61
39
 
62
- h2. Heroku Listener
40
+ h2. Listeners
63
41
 
64
- Your Heroku listener class should handle the following four methods:
42
+ Your listener class should handle the following four methods:
65
43
 
66
- h3. @provision(heroku_id, plan, region, callback_url, logplex_token, options)@
44
+ h3. @provision(params)@
67
45
 
68
- This gets called when Heroku's requesting an app be provisioned within your service, and expects a hash to be returned with the following keys:
46
+ This gets called when the provider is requesting an app be provisioned within your service, and expects a hash to be returned with the following keys:
69
47
 
70
48
  <dl>
71
49
  <dt><code>id</code></dt>
72
- <dd>Your local resource id, which Heroku will use in related requests (to change plans, deprovision or initialise single-sign-on).</dd>
50
+ <dd>Your local resource id, which the provider will use in related requests (to change plans, deprovision or initialise single-sign-on).</dd>
73
51
  <dt><code>config</code></dt>
74
52
  <dd>A hash of the newly provisioned resource's configuration values (that are provided as environment variables to the app in question).</dd>
75
53
  <dt><code>message</code></dt>
76
54
  <dd>An optional message that will be displayed when your add-on is added via the command-line.</dd>
77
55
  </dl>
78
56
 
79
- h3. @plan_change(resource_id, heroku_id, plan)@
57
+ h3. @plan_change(resource_id, plan)@
80
58
 
81
59
  This gets called when an app is upgrading or downgrading from their current plan. You need to return a hash with the following keys:
82
60
 
@@ -105,13 +83,13 @@ Maître d' will check the token and timestamp provided, and sets up the nav-data
105
83
  Here's a very basic example:
106
84
 
107
85
  <pre><code>class HerokuListener
108
- def provision(heroku_id, plan, region, callback_url, logplex_token, options)
109
- plan = Plan.find_by_name plan
86
+ def provision(params)
87
+ plan = Plan.find_by_name params["plan"]
110
88
  widget = Widget.create(
111
- :heroku_id => heroku_id,
112
- :callback_url => callback_url,
89
+ :heroku_id => params["uuid"],
90
+ :callback_url => params["callback_url"],
113
91
  :plan => plan,
114
- :region => region
92
+ :region => params["region"]
115
93
  )
116
94
 
117
95
  {
@@ -121,9 +99,9 @@ Here's a very basic example:
121
99
  }
122
100
  end
123
101
 
124
- def plan_change(resource_id, heroku_id, plan)
102
+ def plan_change(resource_id, plan)
125
103
  plan = Plan.find_by_name plan
126
- widget = Widget.find resource_id
104
+ widget = Widget.find_by! heroku_id: resource_id
127
105
  widget.plan = plan
128
106
  widget.save
129
107
 
@@ -131,12 +109,12 @@ Here's a very basic example:
131
109
  end
132
110
 
133
111
  def deprovision(resource_id)
134
- widget = Widget.find resource_id
112
+ widget = Widget.find_by! heroku_id: resource_id
135
113
  widget.destroy
136
114
  end
137
115
 
138
116
  def single_sign_on(resource_id)
139
- widget = Widget.find resource_id
117
+ widget = Widget.find_by! heroku_id: resource_id
140
118
 
141
119
  {
142
120
  :uri => '/my/dashboard',
@@ -147,14 +125,6 @@ end</code></pre>
147
125
 
148
126
  You can have the listener class wherever you like - as long as it's available within the context of your Rails/Rack site, it'll work as expected.
149
127
 
150
- h2. CloudControl Listener
151
-
152
- CloudControl is pretty much the same as Heroku - it has the same expectations for a listener, so perhaps you can use the same class if you like. Anything that Heroku sends through as the heroku_id becomes cloudcontrol_id - but that's just a matter of treating the given parameter appropriately.
153
-
154
- h2. Opperator Listener
155
-
156
- This listener is currently in progress as the Opperator API is established.
157
-
158
128
  h2. Contributing
159
129
 
160
130
  Contributions are very much welcome - but keep in mind the following:
@@ -165,4 +135,4 @@ Contributions are very much welcome - but keep in mind the following:
165
135
 
166
136
  h2. Credits
167
137
 
168
- Copyright (c) 2011-2014, Maître d' is developed and maintained by Pat Allan, and is released under the open MIT Licence.
138
+ Copyright (c) 2011-2020, Maître d' is developed and maintained by Pat Allan, and is released under the open MIT Licence.
@@ -2,9 +2,16 @@
2
2
  "id": "foo",
3
3
  "api": {
4
4
  "config_vars": [ "FOO_PROVISIONED" ],
5
- "password": "bar",
6
- "sso_salt": "sea salt",
7
- "production": "https://yourapp.com/",
8
- "test": "http://localhost:9292/"
5
+ "regions": [ "us" ],
6
+ "password": "056e8ff3d55ea6e1a7cc4aa7afeaf3ac",
7
+ "sso_salt": "db0ce73d4bd2dfa1d66c713a9aaf8f76",
8
+ "production": {
9
+ "base_url": "https://yourapp.com/heroku/resources",
10
+ "sso_url": "https://yourapp.com/sso/login"
11
+ },
12
+ "test": {
13
+ "base_url": "http://localhost:9292/heroku/resources",
14
+ "sso_url": "http://localhost:9292/heroku/resources/sso"
15
+ }
9
16
  }
10
17
  }
@@ -1,4 +1,7 @@
1
- require 'grape'
1
+ require 'sliver'
2
+ require 'multi_json'
2
3
 
3
4
  module MaitreD
4
5
  end
6
+
7
+ require 'maitre_d/api'
@@ -0,0 +1,32 @@
1
+ class MaitreD::API
2
+ def initialize(configuration)
3
+ @configuration = configuration
4
+ end
5
+
6
+ def call(environment)
7
+ environment['maitre_d.configuration'] = configuration
8
+
9
+ endpoints.call environment
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :configuration
15
+
16
+ def endpoints
17
+ @endpoints ||= Sliver::API.new do |api|
18
+ api.connect :post, '/resources/sso', MaitreD::API::SSO
19
+ api.connect :post, '/resources', MaitreD::API::Create
20
+ api.connect :put, %r{/resources/[\w-]+}, MaitreD::API::ChangePlan
21
+ api.connect :delete, %r{/resources/[\w-]+}, MaitreD::API::Delete
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'maitre_d/api/authentication_guard'
27
+ require 'maitre_d/api/authenticated'
28
+ require 'maitre_d/api/change_plan'
29
+ require 'maitre_d/api/create'
30
+ require 'maitre_d/api/delete'
31
+ require 'maitre_d/api/sso_guard'
32
+ require 'maitre_d/api/sso'
@@ -0,0 +1,30 @@
1
+ class MaitreD::API::Authenticated
2
+ include Sliver::Action
3
+
4
+ def self.guards
5
+ [MaitreD::API::AuthenticationGuard]
6
+ end
7
+
8
+ def call
9
+ response.body = [MultiJson.dump(response.body)]
10
+ response.status ||= 200
11
+ end
12
+
13
+ def configuration
14
+ environment['maitre_d.configuration']
15
+ end
16
+
17
+ private
18
+
19
+ def listener
20
+ configuration.listener.new
21
+ end
22
+
23
+ def params
24
+ @params ||= MultiJson.load request.body.read
25
+ end
26
+
27
+ def provider_id
28
+ configuration.provider_id_from params
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ class MaitreD::API::AuthenticationGuard < Sliver::Hook
2
+ def continue?
3
+ valid_authorization?
4
+ end
5
+
6
+ def respond
7
+ response.status = 401
8
+ response.body = ['401 Unauthorized']
9
+ response.headers['Content-Length'] = response.body.first.length.to_s
10
+ end
11
+
12
+ private
13
+
14
+ def expected_credentials
15
+ "#{action.configuration.id}:#{action.configuration.password}"
16
+ end
17
+
18
+ def provided_credentials
19
+ Base64.decode64 action.request.env['HTTP_AUTHORIZATION'].gsub(/^Basic /, '')
20
+ end
21
+
22
+ def valid_authorization?
23
+ provided_credentials == expected_credentials
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ class MaitreD::API::ChangePlan < MaitreD::API::Authenticated
2
+ def call
3
+ response.body = listener.plan_change(
4
+ resource_id, params['plan']
5
+ )
6
+
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def resource_id
13
+ request.path[%r{resources/([\w-]+)}, 1]
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ class MaitreD::API::Create < MaitreD::API::Authenticated
2
+ def call
3
+ response.body = listener.provision(params)
4
+
5
+ super
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ class MaitreD::API::Delete < MaitreD::API::Authenticated
2
+ def call
3
+ response.body = listener.deprovision resource_id
4
+
5
+ super
6
+ end
7
+
8
+ private
9
+
10
+ def resource_id
11
+ request.path[%r{resources/([\w-]+)}, 1]
12
+ end
13
+ end
@@ -0,0 +1,45 @@
1
+ class MaitreD::API::SSO
2
+ include Sliver::Action
3
+
4
+ def self.guards
5
+ [MaitreD::API::SSOGuard]
6
+ end
7
+
8
+ def call
9
+ hash = listener.single_sign_on params['resource_id']
10
+
11
+ hash[:session] ||= {}
12
+ hash[:session].each { |key, value| session[key] = value }
13
+
14
+ if environment['action_dispatch.cookies']
15
+ environment['action_dispatch.cookies']['heroku-nav-data'] =
16
+ params['nav-data']
17
+ else
18
+ Rack::Utils.set_cookie_header! response.headers, 'heroku-nav-data',
19
+ :value => params['nav-data']
20
+ end
21
+
22
+ response.status = 302
23
+ response.body = ["Redirect to #{hash[:uri]}"]
24
+ response.headers ||= {}
25
+ response.headers['Location'] = hash[:uri]
26
+ end
27
+
28
+ def configuration
29
+ environment['maitre_d.configuration']
30
+ end
31
+
32
+ private
33
+
34
+ def listener
35
+ configuration.listener.new
36
+ end
37
+
38
+ def params
39
+ request.params
40
+ end
41
+
42
+ def session
43
+ environment['rack.session'] ||= {}
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ class MaitreD::API::SSOGuard < Sliver::Hook
2
+ def continue?
3
+ valid_token? && valid_timestamp?
4
+ end
5
+
6
+ def respond
7
+ response.status = 403
8
+ response.body = ['403 Forbidden']
9
+ response.headers['Content-Length'] = response.body.first.length.to_s
10
+ end
11
+
12
+ private
13
+
14
+ def expected_token
15
+ @expected_token ||= Digest::SHA1.hexdigest(
16
+ "#{params['resource_id']}:#{action.configuration.sso_salt}:#{params['timestamp']}"
17
+ ).to_s
18
+ end
19
+
20
+ def params
21
+ action.request.params
22
+ end
23
+
24
+ def valid_timestamp?
25
+ params['timestamp'].to_i >= (Time.now - 5*60).to_i
26
+ end
27
+
28
+ def valid_token?
29
+ expected_token == params['resource_token']
30
+ end
31
+ end
@@ -34,8 +34,8 @@ module MaitreD::Heroku
34
34
  def self.sso_salt=(salt)
35
35
  @sso_salt = salt
36
36
  end
37
- end
38
37
 
39
- require 'maitre_d/heroku/api_helpers'
40
- require 'maitre_d/heroku/api'
41
- require 'maitre_d/heroku/listener'
38
+ def self.provider_id_from(params)
39
+ params['heroku_id']
40
+ end
41
+ end
@@ -3,22 +3,23 @@ $:.push File.expand_path('../lib', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'maitre_d'
6
- s.version = '0.4.0'
6
+ s.version = '0.7.1'
7
7
  s.authors = ['Pat Allan']
8
8
  s.email = ['pat@freelancing-gods.com']
9
9
  s.homepage = 'http://github.com/flying-sphinx/maitre_d'
10
10
  s.summary = 'Rack APIs for Heroku add-ons'
11
11
  s.description = 'A Rack API (through Grape) for Heroku add-on providers.'
12
12
 
13
- s.rubyforge_project = "maitre_d"
14
-
15
13
  s.files = `git ls-files`.split("\n")
16
14
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
15
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
16
  s.require_paths = ['lib']
19
17
 
20
- s.add_development_dependency 'combustion', '0.5.1'
21
- s.add_development_dependency 'kensa', '1.3.0'
22
- s.add_development_dependency 'rails', '3.1.3'
23
- s.add_development_dependency 'rspec-rails', '2.7.0'
18
+ s.add_runtime_dependency 'sliver', '~> 0.2.2'
19
+ s.add_runtime_dependency 'multi_json', '>= 1.3.0'
20
+
21
+ s.add_development_dependency 'combustion', '~> 1.3'
22
+ s.add_development_dependency 'kensa', '2.1.0'
23
+ s.add_development_dependency 'rails', '~> 6.0'
24
+ s.add_development_dependency 'rspec-rails', '~> 4.0'
24
25
  end