maitre_d 0.3.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +2 -1
- data/Gemfile +0 -5
- data/README.textile +28 -47
- data/addon-manifest.json +11 -4
- data/lib/maitre_d.rb +3 -7
- data/lib/maitre_d/api.rb +32 -0
- data/lib/maitre_d/api/authenticated.rb +30 -0
- data/lib/maitre_d/api/authentication_guard.rb +25 -0
- data/lib/maitre_d/api/change_plan.rb +15 -0
- data/lib/maitre_d/api/create.rb +7 -0
- data/lib/maitre_d/api/delete.rb +13 -0
- data/lib/maitre_d/api/sso.rb +45 -0
- data/lib/maitre_d/api/sso_guard.rb +31 -0
- data/lib/maitre_d/heroku.rb +4 -4
- data/maitre_d.gemspec +10 -9
- data/spec/api/heroku/provisioning_spec.rb +48 -30
- data/spec/api/heroku/single_sign_on_spec.rb +30 -20
- data/spec/internal/app/listeners/heroku_listener.rb +4 -4
- data/spec/internal/config/initializers/heroku.rb +4 -2
- data/spec/internal/config/routes.rb +3 -1
- data/spec/spec_helper.rb +1 -2
- metadata +59 -62
- data/config/routes.rb +0 -6
- data/lib/maitre_d/broadstack.rb +0 -40
- data/lib/maitre_d/broadstack/api.rb +0 -49
- data/lib/maitre_d/broadstack/api_helpers.rb +0 -44
- data/lib/maitre_d/cloud_control.rb +0 -40
- data/lib/maitre_d/cloud_control/api.rb +0 -47
- data/lib/maitre_d/cloud_control/api_helpers.rb +0 -26
- data/lib/maitre_d/engine.rb +0 -3
- data/lib/maitre_d/heroku/api.rb +0 -47
- data/lib/maitre_d/heroku/api_helpers.rb +0 -44
- data/lib/maitre_d/heroku/listener.rb +0 -17
- data/lib/maitre_d/opperator.rb +0 -25
- data/lib/maitre_d/opperator/api.rb +0 -25
- data/lib/maitre_d/opperator/api_helpers.rb +0 -11
- data/lib/maitre_d/opperator/listener.rb +0 -13
- data/spec/api/broadstack/provisioning_spec.rb +0 -94
- data/spec/api/broadstack/single_sign_on_spec.rb +0 -49
- data/spec/api/cloud_control/provisioning_spec.rb +0 -94
- data/spec/api/cloud_control/single_sign_on_spec.rb +0 -49
- data/spec/api/opperator/provisioning_spec.rb +0 -55
- data/spec/internal/app/listeners/broadstack_listener.rb +0 -30
- data/spec/internal/app/listeners/cloud_control_listener.rb +0 -29
- data/spec/internal/app/listeners/opperator_listener.rb +0 -16
- data/spec/internal/config/initializers/broadstack.rb +0 -6
- data/spec/internal/config/initializers/cloud_control.rb +0 -6
- data/spec/internal/config/initializers/opperator.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 764dc0a04e766ea678f0bde9321fd3328dd98a138094a129f36845c409dfad27
|
4
|
+
data.tar.gz: ae4e4c87d345644f054f6b8ff2c56ede552d77ebe98012612f665a72dc31e520
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3a147e73fae54e2c366fd269aa1be216295e80dd909285b31e997501d592b2040591a8fc4e9204f4c034b7da36dc47cdcca89df2a07f0b089a0cb5e9102195a
|
7
|
+
data.tar.gz: 42b838f44359b580ef759979c7bf3fd67acfb477ffb6bc972b7ffe9585767ad09b1077619a01507bd358592020af2a01b5c17a784be9e3e3b29f37cb1e5c54e9
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.textile
CHANGED
@@ -1,71 +1,60 @@
|
|
1
1
|
h1. Maître d'
|
2
2
|
|
3
|
-
"!https://secure.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
|
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
|
11
|
+
Add the following to your Gemfile:
|
12
12
|
|
13
|
-
<pre><code>gem '
|
14
|
-
:git => 'git://github.com/intridea/grape.git',
|
15
|
-
:ref => '212c96cdfb253a59bc93790808c568e559d04468'
|
16
|
-
gem 'maitre_d', '~> 0.1.2'</code></pre>
|
13
|
+
<pre><code>gem 'maitre_d', '~> 0.7.0'</code></pre>
|
17
14
|
|
18
15
|
h3. With Rails
|
19
16
|
|
20
|
-
|
17
|
+
Add the Rack app to your routes file:
|
18
|
+
|
19
|
+
<pre><code>mount MaitreD::API.new(MaitreD::Heroku) => '/heroku'</code></pre>
|
21
20
|
|
22
21
|
h3. Without Rails
|
23
22
|
|
24
|
-
|
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.
|
25
24
|
|
26
25
|
h2. Configuration
|
27
26
|
|
28
27
|
You'll need to provide Maître d' with the appropriate provider credentials - in a Rails app, this would go in an initializer, but for Rack/Sinatra apps just get the values set before the routing is defined.
|
29
28
|
|
30
|
-
<pre><code>
|
31
|
-
config.id = 'addon-id'
|
32
|
-
config.password = 'random'
|
33
|
-
config.sso_salt = 'gibberish'
|
34
|
-
config.listener = HerokuListener
|
35
|
-
end
|
29
|
+
<pre><code>require 'maitre_d/heroku'
|
36
30
|
|
37
|
-
MaitreD::
|
31
|
+
MaitreD::Heroku.configure do |config|
|
38
32
|
config.id = 'addon-id'
|
39
33
|
config.password = 'random'
|
40
34
|
config.sso_salt = 'gibberish'
|
41
|
-
config.listener =
|
42
|
-
end
|
43
|
-
|
44
|
-
MaitreD::Opperator.configure do |config|
|
45
|
-
config.shared_secret = 'something-special'
|
46
|
-
config.listener = OpperatorListener
|
35
|
+
config.listener = HerokuListener
|
47
36
|
end</code></pre>
|
48
37
|
|
49
|
-
The
|
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.
|
50
39
|
|
51
|
-
h2.
|
40
|
+
h2. Listeners
|
52
41
|
|
53
|
-
Your
|
42
|
+
Your listener class should handle the following four methods:
|
54
43
|
|
55
|
-
h3. @provision(
|
44
|
+
h3. @provision(params)@
|
56
45
|
|
57
|
-
This gets called when
|
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:
|
58
47
|
|
59
48
|
<dl>
|
60
49
|
<dt><code>id</code></dt>
|
61
|
-
<dd>Your local resource id, which
|
50
|
+
<dd>Your local resource id, which the provider will use in related requests (to change plans, deprovision or initialise single-sign-on).</dd>
|
62
51
|
<dt><code>config</code></dt>
|
63
52
|
<dd>A hash of the newly provisioned resource's configuration values (that are provided as environment variables to the app in question).</dd>
|
64
53
|
<dt><code>message</code></dt>
|
65
54
|
<dd>An optional message that will be displayed when your add-on is added via the command-line.</dd>
|
66
55
|
</dl>
|
67
56
|
|
68
|
-
h3. @plan_change(resource_id,
|
57
|
+
h3. @plan_change(resource_id, plan)@
|
69
58
|
|
70
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:
|
71
60
|
|
@@ -94,13 +83,13 @@ Maître d' will check the token and timestamp provided, and sets up the nav-data
|
|
94
83
|
Here's a very basic example:
|
95
84
|
|
96
85
|
<pre><code>class HerokuListener
|
97
|
-
def provision(
|
98
|
-
plan = Plan.find_by_name plan
|
86
|
+
def provision(params)
|
87
|
+
plan = Plan.find_by_name params["plan"]
|
99
88
|
widget = Widget.create(
|
100
|
-
:heroku_id =>
|
101
|
-
:callback_url => callback_url,
|
89
|
+
:heroku_id => params["uuid"],
|
90
|
+
:callback_url => params["callback_url"],
|
102
91
|
:plan => plan,
|
103
|
-
:region => region
|
92
|
+
:region => params["region"]
|
104
93
|
)
|
105
94
|
|
106
95
|
{
|
@@ -110,9 +99,9 @@ Here's a very basic example:
|
|
110
99
|
}
|
111
100
|
end
|
112
101
|
|
113
|
-
def plan_change(resource_id,
|
102
|
+
def plan_change(resource_id, plan)
|
114
103
|
plan = Plan.find_by_name plan
|
115
|
-
widget = Widget.
|
104
|
+
widget = Widget.find_by! heroku_id: resource_id
|
116
105
|
widget.plan = plan
|
117
106
|
widget.save
|
118
107
|
|
@@ -120,12 +109,12 @@ Here's a very basic example:
|
|
120
109
|
end
|
121
110
|
|
122
111
|
def deprovision(resource_id)
|
123
|
-
widget = Widget.
|
112
|
+
widget = Widget.find_by! heroku_id: resource_id
|
124
113
|
widget.destroy
|
125
114
|
end
|
126
115
|
|
127
116
|
def single_sign_on(resource_id)
|
128
|
-
widget = Widget.
|
117
|
+
widget = Widget.find_by! heroku_id: resource_id
|
129
118
|
|
130
119
|
{
|
131
120
|
:uri => '/my/dashboard',
|
@@ -136,14 +125,6 @@ end</code></pre>
|
|
136
125
|
|
137
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.
|
138
127
|
|
139
|
-
h2. CloudControl Listener
|
140
|
-
|
141
|
-
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.
|
142
|
-
|
143
|
-
h2. Opperator Listener
|
144
|
-
|
145
|
-
This listener is currently in progress as the Opperator API is established.
|
146
|
-
|
147
128
|
h2. Contributing
|
148
129
|
|
149
130
|
Contributions are very much welcome - but keep in mind the following:
|
@@ -154,4 +135,4 @@ Contributions are very much welcome - but keep in mind the following:
|
|
154
135
|
|
155
136
|
h2. Credits
|
156
137
|
|
157
|
-
Copyright (c) 2011-
|
138
|
+
Copyright (c) 2011-2020, Maître d' is developed and maintained by Pat Allan, and is released under the open MIT Licence.
|
data/addon-manifest.json
CHANGED
@@ -2,9 +2,16 @@
|
|
2
2
|
"id": "foo",
|
3
3
|
"api": {
|
4
4
|
"config_vars": [ "FOO_PROVISIONED" ],
|
5
|
-
"
|
6
|
-
"
|
7
|
-
"
|
8
|
-
"
|
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
|
}
|
data/lib/maitre_d.rb
CHANGED
@@ -1,11 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'sliver'
|
2
|
+
require 'multi_json'
|
2
3
|
|
3
4
|
module MaitreD
|
4
5
|
end
|
5
6
|
|
6
|
-
require 'maitre_d/
|
7
|
-
require 'maitre_d/heroku'
|
8
|
-
require 'maitre_d/cloud_control'
|
9
|
-
require 'maitre_d/opperator'
|
10
|
-
|
11
|
-
require 'maitre_d/engine' if defined?(Rails)
|
7
|
+
require 'maitre_d/api'
|
data/lib/maitre_d/api.rb
ADDED
@@ -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/\d+}, MaitreD::API::ChangePlan
|
21
|
+
api.connect :delete, %r{/resources/\d+}, 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,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
|
data/lib/maitre_d/heroku.rb
CHANGED
@@ -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
|
-
|
40
|
-
|
41
|
-
|
38
|
+
def self.provider_id_from(params)
|
39
|
+
params['heroku_id']
|
40
|
+
end
|
41
|
+
end
|