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.
@@ -0,0 +1,2 @@
1
+ class LeashController < ActionController::Base
2
+ end
@@ -0,0 +1,68 @@
1
+ class Leash::Provider::AccessToken < Ohm::Model
2
+ MAX_ASSIGN_TRIES = 20
3
+
4
+ attribute :app_name
5
+ attribute :access_token
6
+ attribute :owner
7
+ attribute :created_at
8
+ attribute :accessed_at
9
+
10
+ index :app_name
11
+ index :owner
12
+ index :access_token
13
+ index :accessed_at
14
+ unique :access_token
15
+
16
+
17
+ def self.assign!(app_name, owner)
18
+ tries = 0
19
+ access_token = nil
20
+
21
+ loop do
22
+ begin
23
+ access_token = SecureRandom.hex(24)
24
+ timestamp = Time.now.to_i
25
+ self.create app_name: app_name, owner: owner_key(owner), access_token: access_token, created_at: timestamp, accessed_at: timestamp
26
+ break
27
+
28
+ rescue Ohm::UniqueIndexViolation => e
29
+ tries += 1
30
+
31
+ fail if tries > MAX_ASSIGN_TRIES
32
+ end
33
+ end
34
+
35
+ access_token
36
+ end
37
+
38
+
39
+ def self.assign_from_auth_code!(auth_code)
40
+ access_token = assign! auth_code.app_name, auth_code.owner
41
+ auth_code.delete
42
+ access_token
43
+ end
44
+
45
+
46
+ def self.valid?(access_token)
47
+ self.find(access_token: access_token).size != 0
48
+ end
49
+
50
+
51
+ def self.find_by_access_token(access_token)
52
+ self.find(access_token: access_token).sort_by(:created_at, order: "DESC").first
53
+ end
54
+
55
+
56
+ def self.find_by_app_name_and_owner(app_name, owner)
57
+ self.find(app_name: app_name, owner: owner_key(owner)).sort_by(:created_at, order: "DESC").first
58
+ end
59
+
60
+
61
+ def self.owner_key(owner)
62
+ if owner.is_a? ActiveRecord::Base
63
+ "#{owner.class.name}##{owner.id}"
64
+ else
65
+ owner
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,44 @@
1
+ class Leash::Provider::AuthCode < Ohm::Model
2
+ MAX_ASSIGN_TRIES = 20
3
+
4
+ attribute :app_name
5
+ attribute :auth_code
6
+ attribute :owner
7
+ attribute :created_at
8
+
9
+ index :owner
10
+ index :auth_code
11
+ unique :auth_code
12
+
13
+
14
+ def self.assign!(app_name, owner)
15
+ tries = 0
16
+ auth_code = nil
17
+
18
+ loop do
19
+ begin
20
+ auth_code = SecureRandom.hex(24)
21
+ timestamp = Time.now.to_i
22
+ self.create app_name: app_name, owner: owner, auth_code: auth_code, created_at: timestamp
23
+ break
24
+
25
+ rescue Ohm::UniqueIndexViolation => e
26
+ tries += 1
27
+
28
+ fail if tries > MAX_ASSIGN_TRIES
29
+ end
30
+ end
31
+
32
+ auth_code
33
+ end
34
+
35
+
36
+ def self.valid?(auth_code)
37
+ self.find(auth_code: auth_code).size != 0
38
+ end
39
+
40
+
41
+ def self.find_by_auth_code(auth_code)
42
+ self.find(auth_code: auth_code).first
43
+ end
44
+ end
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize! :action_controller, :active_record
7
+ run Combustion::Application
@@ -0,0 +1,27 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require "leash/provider/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "leash_provider"
7
+ s.version = Leash::Provider::VERSION
8
+ s.authors = ["Marcin Lewandowski"]
9
+ s.email = ["marcin@saepia.net"]
10
+ s.homepage = "http://github.com/mspanc/leash-provider"
11
+ s.summary = "High-performance OAuth2 provider for a closed set of trusted apps with multiple roles support"
12
+ s.description = "Leash allows you to build an OAuth2 provider for closed set of trusted apps. I can support multiple user types and is designed with high load in mind."
13
+
14
+ s.add_dependency "rails", "~> 4.2"
15
+ s.add_dependency "ohm"
16
+ s.add_dependency "devise"
17
+ s.add_development_dependency "rspec-rails"
18
+ s.add_development_dependency "rake"
19
+ s.add_development_dependency "factory_girl_rails", "~> 4.0"
20
+ s.add_development_dependency "bundler"
21
+ s.add_development_dependency "sqlite3"
22
+ s.add_development_dependency "combustion", "~> 0.5.3"
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.require_paths = ['lib']
26
+ s.required_ruby_version = '>= 1.9.3'
27
+ end
@@ -0,0 +1,20 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Leash
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../../templates", __FILE__)
7
+
8
+ desc "Creates a Leash initializer and copy locale files to your application."
9
+
10
+ def copy_initializer
11
+ template "leash.rb", "config/initializers/leash.rb"
12
+ end
13
+
14
+
15
+ def add_route
16
+ route "leash"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Leash
4
+ module Provider
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("../../templates", __FILE__)
8
+
9
+ desc "Creates a Leash initializer and route."
10
+
11
+ def copy_initializer
12
+ template "leash_provider.rb", "config/initializers/leash_provider.rb"
13
+ end
14
+
15
+
16
+ def add_route
17
+ route "leash_provicer"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ Leash::Provider.configure do |config|
2
+
3
+ ### ==> User classes
4
+ ## Allowed user classes. Please create devise models with such names first.
5
+ ## None by default so you have to set it to something meaningful in order
6
+ ## to use Leash.
7
+ config.user_roles = [ "User", "Admin" ]
8
+
9
+
10
+ ### ==> Redis
11
+ ## Redis database address. Will use redis://localhost:6379/0 by default.
12
+ ## Leash internally uses Ohm gem for encapsulating redis data into models.
13
+ ## At the moment this setting will be shared with all Ohm models, so
14
+ ## if you use any other Ohm models in the app and they need to use different
15
+ ## database, you must hack the gem.
16
+ ##
17
+ ## If you need to initialize connection to redis database specified below,
18
+ ## e.g. in unicorn or puma initializer please use the following syntax:
19
+ ## Leash.establish_connection!
20
+ # config.redis_url = "redis://127.0.0.1:6379/0"
21
+
22
+
23
+ ### ==> Access tokens
24
+ ## Tells whether leash should reuse access tokens if there is already one
25
+ ## present for application/owner combination. True by default.
26
+ # config.reuse_access_tokens = true
27
+
28
+ end
@@ -0,0 +1,8 @@
1
+ module Leash
2
+ module Provider
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Leash
5
+
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+ def leash_provider
4
+ scope :oauth do
5
+ get "authorize", to: "leash/provider/authorize#authorize", as: "leash_provider_authorize"
6
+ post "token", to: "leash/provider/token#token", as: "leash_provider_token"
7
+ end
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,5 @@
1
+ module Leash
2
+ module Provider
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ require "rails"
2
+ require "devise"
3
+ require "ohm"
4
+ require "leash/provider/version"
5
+ require "leash/provider/engine"
6
+ require "leash/provider/routing"
7
+
8
+ module Leash
9
+ module Provider
10
+ mattr_accessor :user_roles
11
+ @@user_roles = []
12
+
13
+ mattr_accessor :redis_url
14
+ @@redis_url = "redis://127.0.0.1:6379/0"
15
+
16
+
17
+ mattr_accessor :reuse_access_tokens
18
+ @@reuse_access_tokens = true
19
+
20
+
21
+ def self.configure
22
+ yield self
23
+ end
24
+
25
+
26
+ def self.establish_connection!
27
+ Ohm.redis = Redic.new(@@redis_url)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,267 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Leash::Provider::AuthorizeController, :type => :controller do
4
+ before do
5
+ @OLD_ENV = ENV
6
+ ENV["APP_TEST_OAUTH2_CLIENT_ID"] = "123456"
7
+ ENV["APP_TEST_OAUTH2_SECRET"] = "qwerty"
8
+ ENV["APP_TEST_OAUTH2_REDIRECT_URL"] = "http://example.com" # TODO test multiple allowed redirect urls
9
+
10
+ allow(Leash::Provider).to receive(:user_roles).and_return([ valid_user_role ])
11
+ end
12
+
13
+ after do
14
+ (ENV.keys - @OLD_ENV.keys).each{ |key| ENV.delete(key) }
15
+ end
16
+
17
+ let(:app_name) { "test" }
18
+ let(:valid_user_role) { "Admin" }
19
+ let(:valid_client_id) { ENV["APP_TEST_OAUTH2_CLIENT_ID"] }
20
+ let(:valid_redirect_uri) { ENV["APP_TEST_OAUTH2_REDIRECT_URL"] }
21
+ let(:invalid_user_role) { "Ufo" }
22
+ let(:invalid_client_id) { "098765" }
23
+ let(:invalid_redirect_uri) { "http://whatever.com" }
24
+ let(:authentication_route) { new_admin_session_path }
25
+
26
+
27
+ describe "GET authorize" do
28
+ context "for authorization code flow" do
29
+ let(:response_type) { "code" }
30
+
31
+ pending "without all necessary params"
32
+
33
+ context "with all necessary params" do
34
+ let(:params) { { user_role: user_role, response_type: response_type, client_id: client_id, redirect_uri: redirect_uri } }
35
+
36
+ context "with invalid client_id" do
37
+ let(:client_id) { invalid_client_id }
38
+
39
+ context "with valid redirect_uri" do
40
+ let(:redirect_uri) { valid_redirect_uri }
41
+
42
+ context "with valid user role" do
43
+ let(:user_role) { valid_user_role }
44
+
45
+ before do
46
+ get :authorize, params
47
+ end
48
+
49
+ it "should return 422 status" do
50
+ expect(response.status).to eq 422
51
+ end
52
+
53
+ it "should return 'unknown_client_id' in the response" do
54
+ expect(response.body).to eq "unknown_client_id"
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ context "with valid client_id" do
61
+ let(:client_id) { valid_client_id }
62
+
63
+ context "with invalid redirect_uri" do
64
+ let(:redirect_uri) { invalid_redirect_uri }
65
+
66
+ context "with valid user role" do
67
+ let(:user_role) { valid_user_role }
68
+
69
+ before do
70
+ get :authorize, params
71
+ end
72
+
73
+ it "should return 422 status" do
74
+ expect(response.status).to eq 422
75
+ end
76
+
77
+ it "should return 'invalid_redirect_uri' in the response" do
78
+ expect(response.body).to eq "invalid_redirect_uri"
79
+ end
80
+ end
81
+ end
82
+
83
+ context "with valid redirect_uri" do
84
+ let(:redirect_uri) { valid_redirect_uri }
85
+
86
+ context "but with unknown user_role" do
87
+ let(:user_role) { invalid_user_role }
88
+
89
+ before do
90
+ get :authorize, params
91
+ end
92
+
93
+ it "should return 422 status" do
94
+ expect(response.status).to eq 422
95
+ end
96
+
97
+ it "should return 'invalid_user_role' in the response" do
98
+ expect(response.body).to eq "invalid_user_role"
99
+ end
100
+ end
101
+
102
+ context "with valid user role" do
103
+ let(:user_role) { valid_user_role }
104
+
105
+
106
+ before do
107
+ get :authorize, params
108
+ end
109
+
110
+
111
+ context "if not authenticated" do
112
+ it "should redirect to devise sign in screen for this user role" do
113
+ expect(response).to redirect_to(authentication_route)
114
+ end
115
+ end
116
+
117
+ context "if authenticated" do
118
+ context "and there is already an access token for this app_name/owner combination" do
119
+ pending "should redirect to the redirect_uri specified in the app with appended '#access_token=(already present access token)'"
120
+ end
121
+
122
+ context "and there is are no access token for this app_name/owner combination" do
123
+ pending "should redirect to the redirect_uri specified in the app with appended '#access_token=(newly generated access token)'"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+
133
+ context "for implicit flow" do
134
+ let(:response_type) { "token" }
135
+
136
+ pending "without all necessary params"
137
+
138
+ context "with all necessary params" do
139
+ let(:params) { { user_role: user_role, response_type: response_type, client_id: client_id, redirect_uri: redirect_uri } }
140
+
141
+ context "with invalid client_id" do
142
+ let(:client_id) { invalid_client_id }
143
+
144
+ context "with valid redirect_uri" do
145
+ let(:redirect_uri) { valid_redirect_uri }
146
+
147
+ context "with valid user role" do
148
+ let(:user_role) { valid_user_role }
149
+
150
+ before do
151
+ get :authorize, params
152
+ end
153
+
154
+ it "should return 422 status" do
155
+ expect(response.status).to eq 422
156
+ end
157
+
158
+ it "should return 'unknown_client_id' in the response" do
159
+ expect(response.body).to eq "unknown_client_id"
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ context "with valid client_id" do
166
+ let(:client_id) { valid_client_id }
167
+
168
+ context "with invalid redirect_uri" do
169
+ let(:redirect_uri) { invalid_redirect_uri }
170
+
171
+ context "with valid user role" do
172
+ let(:user_role) { valid_user_role }
173
+
174
+ before do
175
+ get :authorize, params
176
+ end
177
+
178
+ it "should redirect to the first redirect_uri specified in the app with appended '#error=invalid_redirect_uri'" do
179
+ expect(response).to redirect_to("#{valid_redirect_uri}#error=invalid_redirect_uri")
180
+ end
181
+ end
182
+ end
183
+
184
+ context "with valid redirect_uri" do
185
+ let(:redirect_uri) { valid_redirect_uri }
186
+
187
+ context "but with unknown user_role" do
188
+ let(:user_role) { invalid_user_role }
189
+
190
+ before do
191
+ get :authorize, params
192
+ end
193
+
194
+ it "should redirect to the first redirect_uri specified in the app with appended '#error=invalid_user_role'" do
195
+ expect(response).to redirect_to("#{valid_redirect_uri}#error=invalid_user_role")
196
+ end
197
+ end
198
+
199
+ context "with valid user role" do
200
+ let(:user_role) { valid_user_role }
201
+
202
+
203
+ context "if not authenticated" do
204
+ before do
205
+ get :authorize, params
206
+ end
207
+
208
+ it "should redirect to devise sign in screen for this user role" do
209
+ expect(response).to redirect_to(authentication_route)
210
+ end
211
+ end
212
+
213
+
214
+ context "if authenticated" do
215
+ let(:admin) { create(:admin) }
216
+
217
+ context "and there is already an access token for this app_name/owner combination" do
218
+ context "and Leash::Provider.reuse_access_tokens is set to true" do
219
+ before do
220
+ expect(Leash::Provider).to receive(:reuse_access_tokens).and_return(true)
221
+
222
+ @existing_access_token = Leash::Provider::AccessToken.assign! app_name, admin
223
+
224
+ sign_in admin
225
+ get :authorize, params
226
+ end
227
+
228
+ it "should redirect to the redirect_uri specified in the params with appended '#access_token=(already present access token)'" do
229
+ expect(response).to redirect_to("#{valid_redirect_uri}#access_token=#{URI.encode(@existing_access_token)}")
230
+ end
231
+ end
232
+
233
+ context "and Leash::Provider.reuse_access_tokens is set to false" do
234
+ before do
235
+ expect(Leash::Provider).to receive(:reuse_access_tokens).and_return(false)
236
+
237
+ @existing_access_token = Leash::Provider::AccessToken.assign! app_name, admin
238
+
239
+ sign_in admin
240
+ get :authorize, params
241
+ end
242
+
243
+ it "should redirect to the redirect_uri specified in the params with appended '#access_token=(newly generated access token)'" do
244
+ expect(response).not_to redirect_to("#{valid_redirect_uri}#access_token=#{URI.encode(@existing_access_token)}")
245
+ expect(response).to redirect_to("#{valid_redirect_uri}#access_token=#{Leash::Provider::AccessToken.find_by_app_name_and_owner(app_name, admin).access_token}")
246
+ end
247
+ end
248
+ end
249
+
250
+ context "and there is are no access token for this app_name/owner combination" do
251
+ before do
252
+ sign_in admin
253
+ get :authorize, params
254
+ end
255
+
256
+ it "should redirect to the redirect_uri specified in the app with appended '#access_token=(newly generated access token)'" do
257
+ expect(response).to redirect_to("#{valid_redirect_uri}#access_token=#{URI.encode(Leash::Provider::AccessToken.find_by_app_name_and_owner(app_name, admin).access_token)}")
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end