leash_provider 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +165 -0
- data/README.md +56 -0
- data/Rakefile +7 -0
- data/app/controllers/leash/provider/authorize_controller.rb +92 -0
- data/app/controllers/leash/provider/token_controller.rb +48 -0
- data/app/controllers/leash/provider_controller.rb +63 -0
- data/app/controllers/leash_controller.rb +2 -0
- data/app/models/leash/provider/access_token.rb +68 -0
- data/app/models/leash/provider/auth_code.rb +44 -0
- data/config.ru +7 -0
- data/leash_provider.gemspec +27 -0
- data/lib/generators/leash/install_generator.rb +20 -0
- data/lib/generators/leash/provider/install_generator.rb +22 -0
- data/lib/generators/templates/leash_provider.rb +28 -0
- data/lib/leash/provider/engine.rb +8 -0
- data/lib/leash/provider/routing.rb +11 -0
- data/lib/leash/provider/version.rb +5 -0
- data/lib/leash_provider.rb +30 -0
- data/spec/controllers/leash/provider/authorize_controller_spec.rb +267 -0
- data/spec/internal/app/controllers/application_controller.rb +5 -0
- data/spec/internal/app/models/admin.rb +6 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/initializers/devise.rb +228 -0
- data/spec/internal/config/routes.rb +5 -0
- data/spec/internal/db/schema.rb +28 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/spec_helper.rb +30 -0
- metadata +203 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0c0be375cbb6253ccc5da502a1a99ae947a35374
|
4
|
+
data.tar.gz: 632504ad3d0dc3cfc9dc572c97821c5b040b8487
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 11b1cee0a728b76afe7b9ad1111e3644efda0ee25dde08d6d33ae87766c088ab35e96cad127368fea7d23a81ba04df6e1e1aa2056a4a950df57af3fdead22a9f
|
7
|
+
data.tar.gz: 8245c45f44f494691f2df264471feee46d72925999e99779af3235f6ea82de0561560af0b10f356817e62c982822d9b48d77157df9a0a8af6e7b5769f2bfdb9f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
leash_provider (0.0.1)
|
5
|
+
devise
|
6
|
+
ohm
|
7
|
+
rails (~> 4.2)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionmailer (4.2.1)
|
13
|
+
actionpack (= 4.2.1)
|
14
|
+
actionview (= 4.2.1)
|
15
|
+
activejob (= 4.2.1)
|
16
|
+
mail (~> 2.5, >= 2.5.4)
|
17
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
18
|
+
actionpack (4.2.1)
|
19
|
+
actionview (= 4.2.1)
|
20
|
+
activesupport (= 4.2.1)
|
21
|
+
rack (~> 1.6)
|
22
|
+
rack-test (~> 0.6.2)
|
23
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
24
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
25
|
+
actionview (4.2.1)
|
26
|
+
activesupport (= 4.2.1)
|
27
|
+
builder (~> 3.1)
|
28
|
+
erubis (~> 2.7.0)
|
29
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
30
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
31
|
+
activejob (4.2.1)
|
32
|
+
activesupport (= 4.2.1)
|
33
|
+
globalid (>= 0.3.0)
|
34
|
+
activemodel (4.2.1)
|
35
|
+
activesupport (= 4.2.1)
|
36
|
+
builder (~> 3.1)
|
37
|
+
activerecord (4.2.1)
|
38
|
+
activemodel (= 4.2.1)
|
39
|
+
activesupport (= 4.2.1)
|
40
|
+
arel (~> 6.0)
|
41
|
+
activesupport (4.2.1)
|
42
|
+
i18n (~> 0.7)
|
43
|
+
json (~> 1.7, >= 1.7.7)
|
44
|
+
minitest (~> 5.1)
|
45
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
46
|
+
tzinfo (~> 1.1)
|
47
|
+
arel (6.0.0)
|
48
|
+
bcrypt (3.1.10)
|
49
|
+
builder (3.2.2)
|
50
|
+
combustion (0.5.3)
|
51
|
+
activesupport (>= 3.0.0)
|
52
|
+
railties (>= 3.0.0)
|
53
|
+
thor (>= 0.14.6)
|
54
|
+
devise (3.4.1)
|
55
|
+
bcrypt (~> 3.0)
|
56
|
+
orm_adapter (~> 0.1)
|
57
|
+
railties (>= 3.2.6, < 5)
|
58
|
+
responders
|
59
|
+
thread_safe (~> 0.1)
|
60
|
+
warden (~> 1.2.3)
|
61
|
+
diff-lcs (1.2.5)
|
62
|
+
erubis (2.7.0)
|
63
|
+
factory_girl (4.5.0)
|
64
|
+
activesupport (>= 3.0.0)
|
65
|
+
factory_girl_rails (4.5.0)
|
66
|
+
factory_girl (~> 4.5.0)
|
67
|
+
railties (>= 3.0.0)
|
68
|
+
globalid (0.3.5)
|
69
|
+
activesupport (>= 4.1.0)
|
70
|
+
hiredis (0.6.0)
|
71
|
+
i18n (0.7.0)
|
72
|
+
json (1.8.2)
|
73
|
+
loofah (2.0.2)
|
74
|
+
nokogiri (>= 1.5.9)
|
75
|
+
mail (2.6.3)
|
76
|
+
mime-types (>= 1.16, < 3)
|
77
|
+
mime-types (2.5)
|
78
|
+
mini_portile (0.6.2)
|
79
|
+
minitest (5.6.1)
|
80
|
+
msgpack (0.5.11)
|
81
|
+
nido (0.0.1)
|
82
|
+
nokogiri (1.6.6.2)
|
83
|
+
mini_portile (~> 0.6.0)
|
84
|
+
ohm (2.2.0)
|
85
|
+
msgpack
|
86
|
+
nido
|
87
|
+
redic
|
88
|
+
stal
|
89
|
+
orm_adapter (0.5.0)
|
90
|
+
rack (1.6.1)
|
91
|
+
rack-test (0.6.3)
|
92
|
+
rack (>= 1.0)
|
93
|
+
rails (4.2.1)
|
94
|
+
actionmailer (= 4.2.1)
|
95
|
+
actionpack (= 4.2.1)
|
96
|
+
actionview (= 4.2.1)
|
97
|
+
activejob (= 4.2.1)
|
98
|
+
activemodel (= 4.2.1)
|
99
|
+
activerecord (= 4.2.1)
|
100
|
+
activesupport (= 4.2.1)
|
101
|
+
bundler (>= 1.3.0, < 2.0)
|
102
|
+
railties (= 4.2.1)
|
103
|
+
sprockets-rails
|
104
|
+
rails-deprecated_sanitizer (1.0.3)
|
105
|
+
activesupport (>= 4.2.0.alpha)
|
106
|
+
rails-dom-testing (1.0.6)
|
107
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
108
|
+
nokogiri (~> 1.6.0)
|
109
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
110
|
+
rails-html-sanitizer (1.0.2)
|
111
|
+
loofah (~> 2.0)
|
112
|
+
railties (4.2.1)
|
113
|
+
actionpack (= 4.2.1)
|
114
|
+
activesupport (= 4.2.1)
|
115
|
+
rake (>= 0.8.7)
|
116
|
+
thor (>= 0.18.1, < 2.0)
|
117
|
+
rake (10.4.2)
|
118
|
+
redic (1.5.0)
|
119
|
+
hiredis
|
120
|
+
responders (2.1.0)
|
121
|
+
railties (>= 4.2.0, < 5)
|
122
|
+
rspec-core (3.2.3)
|
123
|
+
rspec-support (~> 3.2.0)
|
124
|
+
rspec-expectations (3.2.1)
|
125
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
126
|
+
rspec-support (~> 3.2.0)
|
127
|
+
rspec-mocks (3.2.1)
|
128
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
129
|
+
rspec-support (~> 3.2.0)
|
130
|
+
rspec-rails (3.2.1)
|
131
|
+
actionpack (>= 3.0, < 4.3)
|
132
|
+
activesupport (>= 3.0, < 4.3)
|
133
|
+
railties (>= 3.0, < 4.3)
|
134
|
+
rspec-core (~> 3.2.0)
|
135
|
+
rspec-expectations (~> 3.2.0)
|
136
|
+
rspec-mocks (~> 3.2.0)
|
137
|
+
rspec-support (~> 3.2.0)
|
138
|
+
rspec-support (3.2.2)
|
139
|
+
sprockets (3.1.0)
|
140
|
+
rack (~> 1.0)
|
141
|
+
sprockets-rails (2.3.1)
|
142
|
+
actionpack (>= 3.0)
|
143
|
+
activesupport (>= 3.0)
|
144
|
+
sprockets (>= 2.8, < 4.0)
|
145
|
+
sqlite3 (1.3.10)
|
146
|
+
stal (0.1.0)
|
147
|
+
redic
|
148
|
+
thor (0.19.1)
|
149
|
+
thread_safe (0.3.5)
|
150
|
+
tzinfo (1.2.2)
|
151
|
+
thread_safe (~> 0.1)
|
152
|
+
warden (1.2.3)
|
153
|
+
rack (>= 1.0)
|
154
|
+
|
155
|
+
PLATFORMS
|
156
|
+
ruby
|
157
|
+
|
158
|
+
DEPENDENCIES
|
159
|
+
bundler
|
160
|
+
combustion (~> 0.5.3)
|
161
|
+
factory_girl_rails (~> 4.0)
|
162
|
+
leash_provider!
|
163
|
+
rake
|
164
|
+
rspec-rails
|
165
|
+
sqlite3
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# leash-provider
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mspanc/leash-provider)
|
4
|
+
|
5
|
+
High-performance Ruby on Rails OAuth2 provider for a closed set of trusted apps with multiple roles support.
|
6
|
+
|
7
|
+
## Use cases
|
8
|
+
|
9
|
+
Leash is built to support the following scenario:
|
10
|
+
|
11
|
+
* You build a system that consists of multiple apps.
|
12
|
+
* List of the apps does not change too often and apps are not created during system runtime.
|
13
|
+
* You want to have a central authentication system for these apps but authorization can vary from app to app.
|
14
|
+
* You have a few fundamentally different user classes (user, admin etc.).
|
15
|
+
* These apps are trusted, in other words: if app talks to auth server with valid credentials, you don't ask user whether he/she allows to enable data flow.
|
16
|
+
|
17
|
+
Potential use cases are:
|
18
|
+
|
19
|
+
* Intranet.
|
20
|
+
* Larger websites that are decoupled into several smaller apps.
|
21
|
+
|
22
|
+
## Fundamental ideas
|
23
|
+
|
24
|
+
* As the app list is fixed, let's store their credentials in ENV. Fast, easy to maintain and compatible with 12factor.
|
25
|
+
* As tokens are not very persistent, let's use redis for storing them.
|
26
|
+
* As such app can be a subject of high load, let's use redis as a backend.
|
27
|
+
* Do not reinvent the wheel, let's use devise for authentication.
|
28
|
+
|
29
|
+
## Supported OAuth 2 flows
|
30
|
+
|
31
|
+
* Authorization Code (for apps running on a web server)
|
32
|
+
* Implicit (for browser-based or mobile apps)
|
33
|
+
|
34
|
+
## Unsupported features
|
35
|
+
|
36
|
+
At the moment, Leash does not support:
|
37
|
+
|
38
|
+
* Any other flows than mentioned above.
|
39
|
+
* Scopes.
|
40
|
+
* Token refreshing and invalidation.
|
41
|
+
|
42
|
+
## Compatible ruby version
|
43
|
+
|
44
|
+
* Leash is tested with ruby 2.2.1.
|
45
|
+
|
46
|
+
## Status
|
47
|
+
|
48
|
+
Work in progress. Early stage of development.
|
49
|
+
|
50
|
+
## License
|
51
|
+
|
52
|
+
MIT
|
53
|
+
|
54
|
+
## Author
|
55
|
+
|
56
|
+
Marcin Lewandowski
|
data/Rakefile
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
class Leash::Provider::AuthorizeController < Leash::ProviderController
|
2
|
+
RESPONSE_TYPES = [ "token", "code" ].freeze
|
3
|
+
|
4
|
+
before_action :determine_response_type!
|
5
|
+
before_action :determine_client_id!
|
6
|
+
before_action :determine_redirect_url!
|
7
|
+
before_action :determine_user_role!
|
8
|
+
before_action :authenticate_user_by_role!
|
9
|
+
|
10
|
+
|
11
|
+
def authorize
|
12
|
+
case @response_type
|
13
|
+
when "token"
|
14
|
+
if Leash::Provider.reuse_access_tokens == true
|
15
|
+
access_token_obj = Leash::Provider::AccessToken.find_by_app_name_and_owner @app_name, current_owner
|
16
|
+
|
17
|
+
if access_token_obj
|
18
|
+
access_token = access_token_obj.access_token
|
19
|
+
else
|
20
|
+
access_token = Leash::Provider::AccessToken.assign! @app_name, current_owner
|
21
|
+
end
|
22
|
+
|
23
|
+
else
|
24
|
+
access_token = Leash::Provider::AccessToken.assign! @app_name, current_owner
|
25
|
+
end
|
26
|
+
|
27
|
+
Rails.logger.info "[Leash::Provider] Authorize ok: response_type=#{@response_type} app_name=#{@app_name} current_owner=#{current_owner} access_token=#{access_token} request_ip=#{request.remote_ip} request_user_agent=#{request.user_agent}"
|
28
|
+
redirect_to params[:redirect_uri] + "#access_token=#{URI.encode(access_token)}"
|
29
|
+
|
30
|
+
when "code"
|
31
|
+
auth_code = Leash::Provider::AuthCode.assign! @app_name, current_owner
|
32
|
+
|
33
|
+
Rails.logger.info "[Leash::Provider] Authorize ok: response_type=#{@response_type} current_owner=#{current_owner} auth_code=#{auth_code} request_ip=#{request.remote_ip} request_user_agent=#{request.user_agent}"
|
34
|
+
redirect_to params[:redirect_uri] + "?code=#{URI.encode(auth_code)}"
|
35
|
+
|
36
|
+
else
|
37
|
+
fail "Should not be reached"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
|
45
|
+
def callback_with_error(error_code, message)
|
46
|
+
Rails.logger.warn "[Leash::Provider] Authorize error: #{error_code} (#{message})"
|
47
|
+
|
48
|
+
case @response_type
|
49
|
+
when "token"
|
50
|
+
if @redirect_url
|
51
|
+
redirect_to @redirect_urls.first + "#error=#{error_code}"
|
52
|
+
else
|
53
|
+
render text: error_code, status: :unprocessable_entity
|
54
|
+
end
|
55
|
+
|
56
|
+
when "code"
|
57
|
+
render text: error_code, status: :unprocessable_entity
|
58
|
+
|
59
|
+
else
|
60
|
+
fail "Should not be reached"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def determine_user_role!
|
66
|
+
params.require("user_role")
|
67
|
+
|
68
|
+
if Leash::Provider.user_roles.include? params[:user_role].to_s
|
69
|
+
@user_role = params[:user_role]
|
70
|
+
@user_role_underscored = params[:user_role].underscore.gsub("/", "_")
|
71
|
+
|
72
|
+
else
|
73
|
+
callback_with_error "invalid_user_role", "Authorize error: Unknown role of '#{params[:user_role]}'"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def determine_response_type!
|
79
|
+
params.require("response_type")
|
80
|
+
|
81
|
+
if RESPONSE_TYPES.include? params[:response_type]
|
82
|
+
@response_type = params[:response_type]
|
83
|
+
else
|
84
|
+
callback_with_error "unknown_response_type", "Unknown response type of '#{params[:response_type]}'"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def authenticate_user_by_role!
|
90
|
+
send "authenticate_#{@user_role_underscored}!"
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Leash::Provider::TokenController < Leash::ProviderController
|
2
|
+
GRANT_TYPES = [ "authorization_code" ].freeze
|
3
|
+
|
4
|
+
before_action :determine_grant_type!
|
5
|
+
before_action :determine_client_id!
|
6
|
+
before_action :determine_client_secret!
|
7
|
+
|
8
|
+
|
9
|
+
def token
|
10
|
+
case @grant_type
|
11
|
+
when "authorization_code"
|
12
|
+
params.require("code")
|
13
|
+
|
14
|
+
if Leash::AuthCode.valid?(params[:code])
|
15
|
+
access_token = Leash::Provider::AccessToken.assign_from_auth_code! Leash::Provider::AuthCode.find_by_auth_code(params[:code])
|
16
|
+
|
17
|
+
render json: { access_token: access_token }
|
18
|
+
end
|
19
|
+
|
20
|
+
else
|
21
|
+
fail "Should not be reached"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
|
29
|
+
def callback_with_error(error_code, message)
|
30
|
+
Rails.logger.warn "[Leash::Provider] Token error: #{error_code} (#{message})"
|
31
|
+
|
32
|
+
case @grant_type
|
33
|
+
when "authorization_code"
|
34
|
+
render json: { error: error_code }, status: :unprocessable_entity
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def determine_grant_type!
|
40
|
+
params.require("grant_type")
|
41
|
+
|
42
|
+
if GRANT_TYPES.include? params[:grant_type]
|
43
|
+
@grant_type = params[:grant_type]
|
44
|
+
else
|
45
|
+
callback_with_error "unknown_grant_type", "Unknown grant type of '#{params[:grant_type]}'"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Leash::ProviderController < LeashController
|
2
|
+
include Devise::Controllers::Helpers
|
3
|
+
|
4
|
+
CLIENT_ID_REGEXP = /\AAPP\_([A-Z0-9\_]+)\_OAUTH2\_CLIENT\_ID\z/.freeze
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def current_owner
|
9
|
+
send("current_#{@user_role_underscored}")
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def determine_client_id!
|
14
|
+
params.require("client_id")
|
15
|
+
|
16
|
+
# FIXME can be suboptimal but for now let it be
|
17
|
+
# Env vars simplicity FTW!
|
18
|
+
ENV.find{ |k,v| k =~ CLIENT_ID_REGEXP and v == params[:client_id] }
|
19
|
+
if $1
|
20
|
+
@client_id = params[:client_id]
|
21
|
+
@env_name = $1.dup
|
22
|
+
@app_name = @env_name.gsub("_", "-").downcase
|
23
|
+
|
24
|
+
else
|
25
|
+
callback_with_error "unknown_client_id", "Unknown client ID '#{params[:client_id]}'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def determine_redirect_url!
|
31
|
+
params.require("redirect_uri")
|
32
|
+
|
33
|
+
@redirect_url = ENV["APP_#{@env_name}_OAUTH2_REDIRECT_URL"]
|
34
|
+
if @redirect_url and not @redirect_url.blank?
|
35
|
+
@redirect_urls = @redirect_url.split(" ")
|
36
|
+
unless @redirect_urls.include? params[:redirect_uri]
|
37
|
+
callback_with_error "invalid_redirect_uri", "Redirect URL mismatch (should be '#{@redirect_url}', given '#{params[:redirect_uri]}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
else
|
41
|
+
callback_with_error "unknown_redirect_uri", "Unable to find redirect URL associated with app '#{@app_name}'"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def determine_client_secret!
|
47
|
+
params.require("client_secret")
|
48
|
+
|
49
|
+
@client_secret = ENV["APP_#{@env_name}_OAUTH2_SECRET"]
|
50
|
+
if @client_secret
|
51
|
+
unless @client_secret == params[:client_secret]
|
52
|
+
callback_with_error "invalid_secret", "Secret mismatch"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
callback_with_error "unknown_secret", "Unable to find secret associated with app '#{@app_name}'"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def callback_with_error(error_code, message)
|
61
|
+
fail "Please override this method"
|
62
|
+
end
|
63
|
+
end
|