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 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
@@ -0,0 +1,4 @@
1
+ /.ruby-version
2
+ *.sqlite
3
+ /spec/internal/log/
4
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 2.2.1
3
+ services:
4
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
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
+ [![Build Status](https://travis-ci.org/mspanc/leash-provider.svg?branch=master)](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,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task test: [:spec]
7
+ task default: :test
@@ -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