leash_provider 0.0.1

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