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,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bcrypt'
4
+ require 'geocoder'
5
+ require 'haml'
6
+ require 'mail'
7
+ require 'mongoid'
8
+ require 'sinatra'
9
+ require 'sinatra/contrib'
10
+ require 'sinatra/flash'
11
+ require 'sinatra/partial'
12
+ require 'rack'
13
+ require 'rack/contrib'
14
+ require 'rack/contrib/cookies'
15
+ require 'rest-client'
16
+ require 'warden'
17
+
18
+ require 'obscured-doorman/loggable'
19
+ require 'obscured-doorman/configuration'
20
+ require 'obscured-doorman/errors'
21
+ require 'obscured-doorman/providers/bitbucket'
22
+ require 'obscured-doorman/providers/github'
23
+ require 'obscured-doorman/strategies/password'
24
+ require 'obscured-doorman/strategies/forgot_password'
25
+ require 'obscured-doorman/strategies/remember_me'
26
+ require 'obscured-doorman/utilities/roles'
27
+ require 'obscured-doorman/utilities/types'
28
+ require 'obscured-doorman/helpers'
29
+ require 'obscured-doorman/mailer'
30
+ require 'obscured-doorman/messages'
31
+ require 'obscured-doorman/version'
32
+ require 'obscured-doorman/base'
33
+
34
+ module Obscured
35
+ module Doorman
36
+ extend Loggable
37
+
38
+ class << self
39
+ # Configuration Object (instance of Obscured::Doorman::Configuration)
40
+ attr_writer :configuration
41
+
42
+ ##
43
+ # Configuration options should be set by passing a hash:
44
+ #
45
+ # Obscured::Doorman.setup do |cfg|
46
+ # cfg.confirmation = false,
47
+ # cfg.registration = true,
48
+ # cfg.smtp_domain = 'domain.tld',
49
+ # cfg.smtp_username = 'username',
50
+ # cfg.smtp_password = 'password',
51
+ # end
52
+ #
53
+ def setup
54
+ yield(configuration)
55
+
56
+ require 'obscured-doorman/models/user'
57
+ require 'obscured-doorman/models/token'
58
+ end
59
+
60
+ def configuration
61
+ @configuration ||= Configuration.new
62
+ end
63
+
64
+ def default_configuration
65
+ configuration.defaults
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ class Warden::SessionSerializer
6
+ def serialize(user)
7
+ user.id
8
+ end
9
+
10
+ def deserialize(id)
11
+ User.find(id)
12
+ end
13
+ end
14
+
15
+ module Base
16
+ module Helpers
17
+ # Generates a flash message by trying to fetch a default message,
18
+ # if that fails just pass the message
19
+ def notify(type, message)
20
+ message = Doorman::MESSAGES[message] if message.is_a?(Symbol)
21
+ flash[type] = message
22
+ end
23
+
24
+ # Generates a url for confirm account or reset password
25
+ def token_link(action, token)
26
+ "http://#{env['HTTP_HOST']}/doorman/#{action}/#{token}"
27
+ end
28
+ end
29
+
30
+ def self.registered(app)
31
+ app.helpers Doorman::Base::Helpers
32
+ app.helpers Doorman::Helpers
33
+
34
+ # Enable Sessions
35
+ app.set :sessions, true unless defined?(Rack::Session::Cookie)
36
+
37
+ app.use Warden::Manager do |config|
38
+ config.scope_defaults :default, action: '/doorman/unauthenticated'
39
+
40
+ config.failure_app = lambda { |_env|
41
+ notify :error, Doorman[:auth_required]
42
+ [302, { 'Location' => Doorman.configuration.paths[:login] }, ['']]
43
+ }
44
+ end
45
+
46
+ Warden::Manager.before_failure do |env, _opts|
47
+ # Because authentication failure can happen on any request but
48
+ # we handle it only under "post '/doorman/unauthenticated'",
49
+ # we need o change request to POST
50
+ env['REQUEST_METHOD'] = 'POST'
51
+ # And we need to do the following to work with Rack::MethodOverride
52
+ env.each do |key, _value|
53
+ env[key]['_method'] = 'post' if key == 'rack.request.form_hash'
54
+ end
55
+ end
56
+ Warden::Strategies.add(:password, Doorman::Strategies::Password)
57
+
58
+ app.post '/doorman/unauthenticated' do
59
+ status 401
60
+ session[:return_to] = env['warden.options'][:attempted_path] if session[:return_to].nil?
61
+ redirect(Doorman.configuration.paths[:login])
62
+ end
63
+
64
+ app.get '/doorman/register/?' do
65
+ redirect(Doorman.configuration.paths[:success]) if authenticated?
66
+
67
+ unless Doorman.configuration.registration
68
+ notify :error, :signup_disabled
69
+ redirect(Doorman.configuration.paths[:login])
70
+ end
71
+
72
+ haml :register
73
+ end
74
+
75
+ app.post '/doorman/register' do
76
+ redirect(Doorman.configuration.paths[:success]) if authenticated?
77
+
78
+ unless Doorman.configuration[:registration]
79
+ notify :error, :signup_disabled
80
+ redirect(Doorman.configuration.paths[:login])
81
+ end
82
+
83
+ begin
84
+ if User.registered?(params[:user][:username])
85
+ notify :error, :register_account_exists
86
+ redirect(Doorman.configuration.paths[:login])
87
+ end
88
+
89
+ user = User.make(
90
+ username: params[:user][:username],
91
+ password: params[:user][:password],
92
+ confirmed: !Doorman.configuration[:confirmation]
93
+ )
94
+ user.name = {
95
+ first_name: params[:user][:first_name],
96
+ last_name: params[:user][:last_name]
97
+ }
98
+ user.save
99
+
100
+ if Doorman.configuration[:confirmation]
101
+ token = user.confirm if Doorman.configuration[:confirmation]
102
+
103
+ if File.exist?('views/doorman/templates/account_activation.haml')
104
+ template = haml :'/templates/account_activation', layout: false, locals: {
105
+ user: user.username,
106
+ link: token_link('confirm', token.token)
107
+ }
108
+ Doorman::Mailer.new(
109
+ to: user.username,
110
+ subject: 'Account activation request',
111
+ text: "You have to activate your account (#{user.username}) before using this service. " + token_link('confirm', token.token),
112
+ html: template
113
+ ).deliver!
114
+ else
115
+ Doorman.logger.warn "Template not found (views/doorman/templates/account_activation.haml), account activation at #{token_link('confirm', token.token)}"
116
+ end
117
+ end
118
+
119
+ # Login when registration is completed
120
+ warden.authenticate(:password)
121
+
122
+ # Set cookie
123
+ cookies[:email] = params[:user][:username]
124
+
125
+ notify :success, :signup_success
126
+ redirect(Doorman.configuration.paths[:success])
127
+ rescue => e
128
+ notify :error, e.message
129
+ redirect(Doorman.configuration.paths[:login])
130
+ end
131
+ end
132
+
133
+ app.get '/doorman/confirm/:token/?' do
134
+ redirect(Doorman.configuration.paths[:success]) if authenticated?
135
+
136
+ if params[:token].nil? || params[:token].empty?
137
+ notify :error, :token_not_found
138
+ redirect(back)
139
+ end
140
+
141
+ token = Token.where(token: params[:token]).first
142
+ if token.nil? && !token&.type.eql?(:confirm)
143
+ notify :error, :token_not_found
144
+ redirect(back)
145
+ end
146
+ if token&.used?
147
+ notify :error, :token_used
148
+ redirect(back)
149
+ end
150
+
151
+ user = token&.user
152
+ if user.nil?
153
+ notify :error, :confirm_no_user
154
+ redirect(Doorman.config.paths[:login])
155
+ end
156
+
157
+ user&.confirm!
158
+ notify :success, :confirm_success
159
+ redirect(Doorman.configuration.paths[:login])
160
+ end
161
+
162
+ app.get '/doorman/login/?' do
163
+ redirect(Doorman.configuration.paths[:success]) if authenticated?
164
+
165
+ email = cookies[:email]
166
+ email = params[:email] if email.nil?
167
+
168
+ haml :login, locals: { email: email }
169
+ end
170
+
171
+ app.post '/doorman/login' do
172
+ warden.authenticate(:password)
173
+
174
+ # Set cookie
175
+ cookies[:email] = params[:user][:username]
176
+
177
+ # Notify if there are any messages from Warden.
178
+ notify :error, warden.message unless warden.message.blank?
179
+
180
+ redirect(Doorman.configuration.use_referrer && session[:return_to] ? session.delete(:return_to) : Doorman.configuration.paths[:success])
181
+ end
182
+
183
+ app.get '/doorman/logout/?' do
184
+ warden.logout(:default)
185
+
186
+ notify :success, :logout_success
187
+ redirect(Doorman.configuration.paths[:login])
188
+ end
189
+ end
190
+ end
191
+
192
+ class Middleware < Sinatra::Base
193
+ helpers Sinatra::Cookies
194
+ register Sinatra::Flash
195
+ register Sinatra::Partial
196
+ register Strategies::ForgotPassword
197
+ register Strategies::RememberMe
198
+ register Doorman::Base
199
+ register Doorman::Providers::Bitbucket
200
+ register Doorman::Providers::GitHub
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ class Configuration
6
+ def self.config_option(name)
7
+ define_method(name) do
8
+ read_value(name)
9
+ end
10
+
11
+ define_method("#{name}=") do |value|
12
+ set_value(name, value)
13
+ end
14
+ end
15
+
16
+ #def self.proc_config_option(name)
17
+ # define_method(name) do |&block|
18
+ # set_value(name, block) unless block.nil?
19
+ # read_value(name)
20
+ # end
21
+ #
22
+ # define_method("#{name}=") do |value|
23
+ # set_value(name, value)
24
+ # end
25
+ #end
26
+
27
+ # Set log level
28
+ config_option :log_level
29
+
30
+ # Enables/disables user confirmation
31
+ config_option :confirmation
32
+ # Enables/disables user registration
33
+ config_option :registration
34
+ # Enables/disables the usage of referrer redirection
35
+ config_option :use_referrer
36
+
37
+ # Remember me cookie name
38
+ config_option :remember_cookie
39
+ # Remember me for x-days
40
+ config_option :remember_for
41
+
42
+ # Database name
43
+ config_option :db_name
44
+ # Database client
45
+ config_option :db_client
46
+
47
+ # SMTP Domain
48
+ config_option :smtp_domain
49
+ # SMTP Server
50
+ config_option :smtp_server
51
+ # SMTP Server PSort
52
+ config_option :smtp_port
53
+ # SMTP Sender Username
54
+ config_option :smtp_username
55
+ # SMTP Sender Password
56
+ config_option :smtp_password
57
+
58
+ # Authentication Providers
59
+ config_option :providers
60
+
61
+ # Authentication Providers
62
+ config_option :paths
63
+
64
+ attr_reader :defaults
65
+
66
+ def initialize
67
+ @config_values = {}
68
+
69
+ # set default attribute values
70
+ @defaults = _defaults
71
+ end
72
+
73
+ def [](key)
74
+ read_value(key)
75
+ end
76
+
77
+ def []=(key, value)
78
+ set_value(key, value)
79
+ end
80
+
81
+ private
82
+
83
+ def read_value(name)
84
+ if @config_values.key?(name)
85
+ @config_values[name]
86
+ else
87
+ @defaults.send(name)
88
+ end
89
+ end
90
+
91
+ def set_value(name, value)
92
+ @config_values[name] = value
93
+ end
94
+
95
+ def _defaults
96
+ OpenStruct.new(
97
+ log_level: Logger::DEBUG,
98
+ confirmation: false,
99
+ registration: false,
100
+ use_referrer: true,
101
+ remember_cookie: 'sinatra.doorman.remember',
102
+ remember_for: 30,
103
+ db_name: 'doorman',
104
+ db_client: :doorman,
105
+ smtp_domain: 'doorman.local',
106
+ smtp_server: '127.0.0.1',
107
+ smtp_port: 587,
108
+ smtp_username: nil,
109
+ smtp_password: nil,
110
+ providers: [],
111
+ paths: {
112
+ success: '/home',
113
+ login: '/doorman/login',
114
+ logout: '/doorman/logout',
115
+ forgot: '/doorman/forgot',
116
+ reset: '/doorman/reset',
117
+ register: '/doorman/register'
118
+ }
119
+ )
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ class Error < StandardError
6
+ attr_reader :code
7
+ attr_reader :field
8
+ attr_reader :error
9
+
10
+ ERRORS = {
11
+ invalid_api_method: 'No method parameter was supplied',
12
+ unspecified_error: 'Unspecified error',
13
+ already_exists: '{what}',
14
+ does_not_exist: '{what}',
15
+ does_not_match: '{what}',
16
+ account: '{what}',
17
+ invalid_date: 'Cannot parse {what} from: {date}',
18
+ invalid_type: '{what}',
19
+ not_active: 'Not active',
20
+ required_field_missing: 'Required field {field} is missing'
21
+ }.freeze
22
+
23
+ def initialize(code, params = {})
24
+ field = params.delete(:field)
25
+ error = params.delete(:error)
26
+
27
+ super(parse(code, params))
28
+ @code = code || :unspecified_error
29
+ @field = field || :unspecified_field
30
+ @error = error
31
+ end
32
+
33
+ private
34
+
35
+ def parse(code, params = {})
36
+ message = ERRORS[code]
37
+ params.each_pair do |key, value|
38
+ message = message.sub("{#{key}}", value)
39
+ end
40
+ message
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Obscured
4
+ module Doorman
5
+ module Helpers
6
+ # The main accessor to the warden middleware
7
+ def warden
8
+ request.env['warden']
9
+ end
10
+
11
+ # Check the current session is authenticated to a given scope
12
+ def authenticated?(scope = nil)
13
+ scope ? warden.authenticated?(scope: scope) : warden.authenticated?
14
+ end
15
+ alias logged_in? authenticated?
16
+
17
+ # Authenticate a user against defined strategies
18
+ def authenticate(*args)
19
+ warden.authenticate!(*args)
20
+ end
21
+ alias login authenticate
22
+
23
+ # Return session info
24
+ #
25
+ # @param [Symbol] scope the scope to retrieve session info for
26
+ def session_info(scope = nil)
27
+ scope ? warden.session(scope) : scope
28
+ end
29
+
30
+ # Terminate the current session
31
+ #
32
+ # @param [Symbol] scopes the session scope to terminate
33
+ def logout(scopes = nil)
34
+ scopes ? warden.logout(scopes) : warden.logout(warden.config.default_scope)
35
+ end
36
+
37
+ # Access the user from the current session
38
+ #
39
+ # @param [Symbol] scope for the logged in user
40
+ def user(scope = nil)
41
+ scope ? warden.user(scope) : warden.user
42
+ end
43
+ alias current_user user
44
+
45
+ # Require authorization for an action
46
+ #
47
+ # @param [String] failure_path path to redirect to if user is unauthenticated
48
+ def authorize!(failure_path = nil)
49
+ unless authenticated?
50
+ session[:return_to] = request.path if Doorman.configuration.use_referrer
51
+ redirect(failure_path || Doorman.configuration.paths[:login])
52
+ end
53
+ end
54
+
55
+ # Require authorization for example ajax calls, returns 403 is not authenticated
56
+ #
57
+ # @param [Symbol] format
58
+ def authorized?(format = :json)
59
+ unless authenticated?
60
+ halt 403, { 'Content-Type' => 'application/json' }, { message: 'Unauthorized' }.to_json if format == :json
61
+ halt 403
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end