leash_provider 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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