obscured-doorman 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +28 -0
  3. data/.github/dependabot.yml +11 -0
  4. data/.github/workflows/publish.yml +44 -0
  5. data/.gitignore +4 -0
  6. data/.rubocop.yml +14 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/.simplecov +6 -0
  10. data/.travis.yml +17 -0
  11. data/CHANGELOG.md +31 -0
  12. data/Gemfile +8 -0
  13. data/Gemfile.lock +144 -0
  14. data/README.md +115 -0
  15. data/lib/obscured-doorman.rb +69 -0
  16. data/lib/obscured-doorman/base.rb +203 -0
  17. data/lib/obscured-doorman/configuration.rb +123 -0
  18. data/lib/obscured-doorman/errors.rb +44 -0
  19. data/lib/obscured-doorman/helpers.rb +66 -0
  20. data/lib/obscured-doorman/loggable.rb +51 -0
  21. data/lib/obscured-doorman/mailer.rb +46 -0
  22. data/lib/obscured-doorman/messages.rb +30 -0
  23. data/lib/obscured-doorman/models/token.rb +57 -0
  24. data/lib/obscured-doorman/models/user.rb +160 -0
  25. data/lib/obscured-doorman/providers/base/configuration.rb +69 -0
  26. data/lib/obscured-doorman/providers/bitbucket.rb +79 -0
  27. data/lib/obscured-doorman/providers/bitbucket/access_token.rb +27 -0
  28. data/lib/obscured-doorman/providers/bitbucket/configuration.rb +38 -0
  29. data/lib/obscured-doorman/providers/bitbucket/messages.rb +13 -0
  30. data/lib/obscured-doorman/providers/bitbucket/strategy.rb +53 -0
  31. data/lib/obscured-doorman/providers/github.rb +78 -0
  32. data/lib/obscured-doorman/providers/github/access_token.rb +23 -0
  33. data/lib/obscured-doorman/providers/github/configuration.rb +38 -0
  34. data/lib/obscured-doorman/providers/github/messages.rb +13 -0
  35. data/lib/obscured-doorman/providers/github/strategy.rb +53 -0
  36. data/lib/obscured-doorman/strategies/forgot_password.rb +157 -0
  37. data/lib/obscured-doorman/strategies/password.rb +38 -0
  38. data/lib/obscured-doorman/strategies/remember_me.rb +54 -0
  39. data/lib/obscured-doorman/utilities/roles.rb +11 -0
  40. data/lib/obscured-doorman/utilities/types.rb +14 -0
  41. data/lib/obscured-doorman/version.rb +7 -0
  42. data/obscured-doorman.gemspec +42 -0
  43. data/spec/config/mongoid.yml +11 -0
  44. data/spec/doorman_spec.rb +203 -0
  45. data/spec/errors_spec.rb +11 -0
  46. data/spec/factories/token_factory.rb +8 -0
  47. data/spec/factories/user_factory.rb +12 -0
  48. data/spec/helpers/application_helper.rb +52 -0
  49. data/spec/helpers/request_helper.rb +53 -0
  50. data/spec/loggable_spec.rb +27 -0
  51. data/spec/mailer_spec.rb +26 -0
  52. data/spec/matchers/time.rb +7 -0
  53. data/spec/setup.rb +58 -0
  54. data/spec/token_spec.rb +62 -0
  55. data/spec/user_spec.rb +151 -0
  56. metadata +361 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'haml'
4
+
5
+ module Obscured
6
+ module Doorman
7
+ module Strategies
8
+ class Password < Warden::Strategies::Base
9
+ include Haml::Helpers
10
+
11
+ def valid?
12
+ params['user'] &&
13
+ params['user']['username'] &&
14
+ params['user']['password']
15
+ end
16
+
17
+ def authenticate!
18
+ fail!(Doorman::MESSAGES[:login_bad_credentials]) unless valid?
19
+
20
+ user = User.authenticate(
21
+ params['user']['username'],
22
+ params['user']['password']
23
+ )
24
+
25
+ if user.nil?
26
+ fail!(Doorman::MESSAGES[:login_bad_credentials])
27
+ elsif Doorman.configuration[:confirmation] && !user.confirmed
28
+ user.confirm
29
+
30
+ fail!(Doorman::MESSAGES[:login_not_confirmed])
31
+ else
32
+ success!(user)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ module Strategies
6
+ class RememberMeStrategy < Warden::Strategies::Base
7
+ def valid?
8
+ !env['rack.cookies'][Doorman.configuration.remember_cookie].nil?
9
+ end
10
+
11
+ def authenticate!
12
+ token = env['rack.cookies'][Doorman.configuration.remember_cookie]
13
+ return unless token
14
+
15
+ token = Token.where(token: token).first
16
+ user = token&.user
17
+ env['rack.cookies'].delete(Doorman.configuration.remember_cookie) && return if user.nil?
18
+ success!(user)
19
+ end
20
+ end
21
+
22
+ module RememberMe
23
+ def self.registered(app)
24
+ app.use Rack::Cookies
25
+
26
+ Warden::Strategies.add(:remember_me, Strategies::RememberMeStrategy)
27
+
28
+ app.before do
29
+ warden.authenticate(:remember_me)
30
+ end
31
+
32
+ Warden::Manager.after_authentication do |user, auth, _opts|
33
+ if auth.winning_strategy.is_a?(Strategies::RememberMeStrategy) ||
34
+ (auth.winning_strategy.is_a?(Strategies::Password) && auth.params['user']['remember_me'])
35
+
36
+ token = user.tokens.where(type: :remember).first
37
+ user.remember_me! # new token
38
+ auth.env['rack.cookies'][Doorman.configuration.remember_cookie] = {
39
+ value: token.token,
40
+ expires: (Time.now + Doorman.configuration.remember_for.days.seconds),
41
+ path: '/'
42
+ }
43
+ end
44
+ end
45
+
46
+ Warden::Manager.before_logout do |user, auth, _opts|
47
+ user&.forget_me! if user
48
+ auth.env['rack.cookies'].delete(Doorman.configuration.remember_cookie)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ module Roles
6
+ SYSTEM = :system
7
+ ADMIN = :admin
8
+ VIEWER = :viewer
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ module Types
6
+ SYSTEM = :system
7
+ ADMIN = :admin
8
+ CONSOLE = :console
9
+ SCRIPT = :script
10
+ SETUP = :setup
11
+ HEROKU = :heroku
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ VERSION = "0.4.0".freeze
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'obscured-doorman/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = 'obscured-doorman'
9
+ gem.version = Obscured::Doorman::VERSION
10
+ gem.authors = ['Erik Hennerfors']
11
+ gem.email = ['erik.hennerfors@obscured.se']
12
+ gem.description = "Doorman is a front of Warden used by Obscured and it's applications"
13
+ gem.summary = "Doorman is a front of Warden used by Obscured and it's applications"
14
+ gem.homepage = 'https://github.com/gonace/Obscured.Doorman.git'
15
+
16
+ gem.required_ruby_version = '>= 2'
17
+
18
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.require_paths = ['lib']
22
+
23
+ gem.add_dependency 'bcrypt'
24
+ gem.add_dependency 'geocoder'
25
+ gem.add_dependency 'haml'
26
+ gem.add_dependency 'mail'
27
+ gem.add_dependency 'mongoid'
28
+ gem.add_dependency 'rack'
29
+ gem.add_dependency 'rack-contrib'
30
+ gem.add_dependency 'rest-client'
31
+ gem.add_dependency 'sinatra'
32
+ gem.add_dependency 'sinatra-contrib'
33
+ gem.add_dependency 'sinatra-flash'
34
+ gem.add_dependency 'sinatra-partial'
35
+ gem.add_dependency 'warden'
36
+
37
+ gem.add_development_dependency 'dotenv'
38
+ gem.add_development_dependency 'factory_bot'
39
+ gem.add_development_dependency 'rack-test'
40
+ gem.add_development_dependency 'rspec'
41
+ gem.add_development_dependency 'simplecov'
42
+ end
@@ -0,0 +1,11 @@
1
+ spec:
2
+ options:
3
+ raise_not_found_error: false
4
+ clients:
5
+ default:
6
+ database: doorman_testing
7
+ hosts:
8
+ - localhost:27017
9
+ options:
10
+ max_pool_size: 100
11
+ min_pool_size: 5
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'setup'
4
+
5
+
6
+ describe Obscured::Doorman::Base do
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ Obscured::Doorman::Spec::App
11
+ end
12
+
13
+ let!(:email) { 'homer.simpson@obscured.se' }
14
+ let!(:password) { 'Password123' }
15
+ let!(:user) { FactoryBot.create(:user, username: email) }
16
+
17
+ describe 'login' do
18
+ def do_login(overrides = {})
19
+ cmd = {}
20
+ cmd[:user] = {}
21
+ cmd[:user][:username] = overrides[:username] if overrides[:username]
22
+ cmd[:user][:password] = overrides[:password] if overrides[:password]
23
+ post '/doorman/login', cmd
24
+ end
25
+
26
+ context 'successful' do
27
+ before(:each) do
28
+ do_login(username: email, password: password)
29
+ end
30
+
31
+ it 'redirects user to front page (/home)' do
32
+ expect(last_response.status).to eq(302)
33
+ expect(last_response).to be_redirect
34
+ expect(last_response.location).to eq('http://example.org/home')
35
+ follow_redirect!
36
+ expect(last_response.body).to eq('Hello World!')
37
+ end
38
+
39
+ it 'returns session info' do
40
+ get '/session'
41
+
42
+ expect(last_response.status).to eq(200)
43
+ end
44
+
45
+ it 'returns user info' do
46
+ get '/user'
47
+
48
+ expect(last_response.status).to eq(200)
49
+ end
50
+ end
51
+
52
+ context 'unsuccessful' do
53
+ context 'wrong password' do
54
+ before(:each) do
55
+ do_login(username: email, password: 'foo/bar')
56
+ end
57
+
58
+ it 'redirects back to login if authentication failed' do
59
+ expect(last_response.status).to eq(302)
60
+ follow_redirect!
61
+ expect(last_response.location).to eq('http://example.org/doorman/login')
62
+ end
63
+
64
+ it 'does not return user info' do
65
+ get '/user'
66
+
67
+ expect(last_response.status).to eq(403)
68
+ end
69
+
70
+ it 'does not return user info' do
71
+ get '/user/xml'
72
+
73
+ expect(last_response.status).to eq(403)
74
+ end
75
+ end
76
+
77
+ context 'user not confirmed' do
78
+ before(:each) do
79
+ Obscured::Doorman.configuration[:confirmation] = true
80
+ do_login(username: email, password: password)
81
+ end
82
+
83
+ after(:each) do
84
+ Obscured::Doorman.configuration[:confirmation] = false
85
+ end
86
+
87
+ it 'redirects back to login when authentication failed due to confirmation not completed' do
88
+ expect(last_response.status).to eq(302)
89
+ follow_redirect!
90
+ expect(last_response.location).to eq('http://example.org/doorman/login')
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe 'logout' do
97
+ def do_login(overrides = {})
98
+ cmd = {}
99
+ cmd[:user] = {}
100
+ cmd[:user][:username] = overrides[:username] if overrides[:username]
101
+ cmd[:user][:password] = overrides[:password] if overrides[:password]
102
+ post '/doorman/login', cmd
103
+ end
104
+
105
+ context 'successful' do
106
+ before(:each) do
107
+ do_login(username: email, password: password)
108
+ end
109
+
110
+ it 'logs out the user' do
111
+ get '/logout'
112
+
113
+ expect(last_response.status).to eq(200)
114
+ end
115
+ end
116
+ end
117
+
118
+ describe 'register' do
119
+ def do_register(overrides = {})
120
+ cmd = {}
121
+ cmd[:user] = {}
122
+ cmd[:user][:username] = overrides[:username] if overrides[:username]
123
+ cmd[:user][:password] = overrides[:password] if overrides[:password]
124
+ post '/doorman/register', cmd
125
+ end
126
+
127
+ context 'successful' do
128
+ before(:each) do
129
+ do_register(username: 'lisa.simpson@obscured.se', password: "#{password}#!")
130
+ end
131
+
132
+ it 'redirects user to front page (/home)' do
133
+ expect(last_response.status).to eq(302)
134
+ expect(last_response).to be_redirect
135
+ expect(last_response.location).to eq('http://example.org/home')
136
+
137
+ follow_redirect!
138
+
139
+ expect(last_response.body).to eq('Hello World!')
140
+ end
141
+ end
142
+ end
143
+
144
+ describe 'confirm' do
145
+ def do_confirm(token)
146
+ get "/doorman/confirm/#{token}"
147
+ end
148
+
149
+ let!(:token) { FactoryBot.create(:token, type: :confirm, user: user) }
150
+
151
+ context 'successful' do
152
+ before(:each) do
153
+ do_confirm(token.token)
154
+
155
+ user.reload
156
+ end
157
+
158
+ it 'confirms user and removes token' do
159
+ expect(last_response.status).to eq(302)
160
+ expect(user.tokens.where(type: :confirm).count).to eq(0)
161
+ end
162
+ end
163
+ end
164
+
165
+ describe 'forget' do
166
+ def do_forgot(overrides = {})
167
+ cmd = {}
168
+ cmd[:user] = {}
169
+ cmd[:user][:username] = overrides[:username] if overrides[:username]
170
+ post '/doorman/forgot', cmd
171
+ end
172
+
173
+ let(:token) { user.tokens.where(type: :password).first }
174
+
175
+ context 'successful' do
176
+ before(:each) do
177
+ do_forgot(username: user.username)
178
+ user.reload
179
+ end
180
+
181
+ it 'confirms user and removes token' do
182
+ expect(last_response.status).to eq(302)
183
+ expect(user.tokens.where(type: :password).count).to eq(1)
184
+ end
185
+ end
186
+ end
187
+
188
+ describe 'reset' do
189
+ def do_reset(overrides = {})
190
+ cmd = {}
191
+ cmd[:user] = {}
192
+ cmd[:user][:username] = overrides[:username] if overrides[:username]
193
+ cmd[:user][:token] = overrides[:token] if overrides[:token]
194
+ post '/doorman/reset', cmd
195
+ end
196
+ end
197
+
198
+ describe 'configuration' do
199
+ it 'should return default configuration' do
200
+ expect(Obscured::Doorman.default_configuration).to_not be(nil)
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'setup'
4
+
5
+ describe Obscured::Doorman::Error do
6
+ let!(:error) { Obscured::Doorman::Error .new(:already_exists, what: 'foo/bar') }
7
+
8
+ it 'initializes' do
9
+ expect(error.message).to eq('foo/bar')
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :token, class: Obscured::Doorman::Token do
5
+ type { :password }
6
+ token { Digest::SHA1.hexdigest('--homer.simpsons@obscured.se--') }
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :user, class: Obscured::Doorman::User do
5
+ username { 'homer.simpson@obscured.se' }
6
+ password { BCrypt::Password.create('Password123') }
7
+ first_name { 'Homer' }
8
+ last_name { 'Simpson' }
9
+ mobile { '+467855568' }
10
+ role { :admin }
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ module Spec
6
+ class App < Sinatra::Base
7
+ helpers Obscured::Doorman::Helpers
8
+ register Sinatra::Flash
9
+
10
+ use Rack::Session::Cookie,
11
+ key: 'rack.session',
12
+ path: '/',
13
+ secret: 'rspec_secret'
14
+ use Obscured::Doorman::Middleware
15
+
16
+ Obscured::Doorman::Middleware.set :views, "#{File.dirname(__FILE__)}/../views/doorman"
17
+
18
+ get '/home' do
19
+ authorize!
20
+
21
+ 'Hello World!'
22
+ end
23
+
24
+ get '/session' do
25
+ authenticate
26
+
27
+ { session: session_info, user: user }.to_json
28
+ end
29
+
30
+ get '/user' do
31
+ authorized?
32
+ content_type :json
33
+
34
+ { user: user }.to_json
35
+ end
36
+
37
+ get '/user/xml' do
38
+ authorized?(:xml)
39
+ content_type :xml
40
+
41
+ { user: user }.to_xml
42
+ end
43
+
44
+ get '/logout' do
45
+ authorized?
46
+
47
+ logout
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end