obscured-doorman 0.4.0

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.
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