padrino-auth 0.0.12

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
+ require 'padrino-auth/access'
2
+ require 'padrino-auth/login'
@@ -0,0 +1,148 @@
1
+ require 'padrino-auth/permissions'
2
+
3
+ module Padrino
4
+ ##
5
+ # Padrino authorization module.
6
+ #
7
+ # @example
8
+ # class Nifty::Application < Padrino::Application
9
+ # # optional settings
10
+ # set :credentials_reader, :visitor # the name of getter method in helpers
11
+ # # required statement
12
+ # register Padrino::Access
13
+ # # example persistance storage
14
+ # enable :sessions
15
+ # end
16
+ #
17
+ # # optional helpers
18
+ # Nifty::Application.helpers do
19
+ # def visitor
20
+ # session[:visitor] ||= Visitor.guest_account
21
+ # end
22
+ # end
23
+ #
24
+ # # example visitor model
25
+ # module Visitor
26
+ # extend self
27
+ # def guest_account
28
+ # OpenStruct.new(:role => :guest, :id => 1)
29
+ # end
30
+ # end
31
+ #
32
+ # # example controllers
33
+ # Nifty::Application.controller :public_area do
34
+ # set_access :*
35
+ # get(:index){ 'public content' }
36
+ # end
37
+ # Nifty::Application.controller :members_area do
38
+ # set_access :member
39
+ # get(:index){ 'secret content' }
40
+ # end
41
+ # Nifty::Application.controller :login do
42
+ # set_access :*
43
+ # get(:index){ session[:visitor] = OpenStruct.new(:role => :guest, :id => 1) }
44
+ # end
45
+ #
46
+ module Access
47
+ class << self
48
+ def registered(app)
49
+ included(app)
50
+ app.default(:credentials_reader, :credentials)
51
+ app.default(:access_errors, true)
52
+ app.send :attr_reader, app.credentials_reader unless app.instance_methods.include?(app.credentials_reader)
53
+ app.set :permissions, Permissions.new
54
+ app.login_permissions if app.respond_to?(:login_permissions)
55
+ app.before do
56
+ authorized? || error(403, '403 Forbidden')
57
+ end
58
+ end
59
+
60
+ def included(base)
61
+ base.send(:include, InstanceMethods)
62
+ base.extend(ClassMethods)
63
+ end
64
+ end
65
+
66
+ module ClassMethods
67
+ ##
68
+ # Empties the list of permission.
69
+ #
70
+ def reset_access!
71
+ permissions.clear!
72
+ end
73
+
74
+ ##
75
+ # Allows access to action with objects.
76
+ #
77
+ # @example
78
+ # # in application
79
+ # set_access :*, :with => :login # allows everyone to interact with :login controller
80
+ # # in controller
81
+ # App.controller :members_area do
82
+ # set_access :member # allows all members to access :members_area controller
83
+ # end
84
+ #
85
+ def set_access(*args)
86
+ options = args.extract_options!
87
+ options[:object] ||= Array(@_controller).first.to_s.singularize.to_sym if @_controller.present?
88
+ permissions.add(*args, options)
89
+ end
90
+ end
91
+
92
+ module InstanceMethods
93
+ ##
94
+ # Checks if current visitor has access to current action with current controller.
95
+ #
96
+ def authorized?
97
+ access_action?
98
+ end
99
+
100
+ ##
101
+ # Returns current visitor.
102
+ #
103
+ def access_subject
104
+ send settings.credentials_reader
105
+ end
106
+
107
+ ##
108
+ # Checks if current visitor is one of the specified roles. Can accept a block.
109
+ #
110
+ def access_role?(*roles, &block)
111
+ settings.permissions.check(access_subject, :have => roles, &block)
112
+ end
113
+
114
+ ##
115
+ # Checks if current visitor is allowed to to the action with object. Can accept a block.
116
+ #
117
+ def access_action?(action = nil, object = nil, &block)
118
+ return true if response.status/100 == 4 && settings.access_errors
119
+ if respond_to?(:request) && action.nil? && object.nil?
120
+ object = request.controller
121
+ action = request.action
122
+ if object.nil? && action.present? && action.to_s.index('/')
123
+ object, action = request.env['PATH_INFO'].to_s.scan(/\/([^\/]*)/).map(&:first)
124
+ end
125
+ object ||= :''
126
+ action ||= :index
127
+ object = object.to_sym
128
+ action = action.to_sym
129
+ end
130
+ settings.permissions.check(access_subject, :allow => action, :with => object, &block)
131
+ end
132
+
133
+ ##
134
+ # Check if current visitor is allowed to interact with object by action. Can accept a block.
135
+ #
136
+ def access_object?(object = nil, action = nil, &block)
137
+ allow_action action, object, &block
138
+ end
139
+
140
+ ##
141
+ # Populates the list of objects the current visitor is allowed to interact with.
142
+ #
143
+ def access_objects(subject = access_subject, action = nil)
144
+ settings.permissions.find_objects(subject, action)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,138 @@
1
+ require 'padrino-auth/login/controller'
2
+
3
+ module Padrino
4
+ ##
5
+ # Padrino authentication module.
6
+ #
7
+ # @example
8
+ # class Nifty::Application < Padrino::Application
9
+ # # optional settings
10
+ # set :session_key, "visitor_id" # visitor key name in session storage, defaults to "_login_#{app.app_name}")
11
+ # set :login_model, :visitor # model name for visitor storage, defaults to :account, must be constantizable
12
+ # set :credentials_accessor, :visitor # the name of setter/getter method in helpers, defaults to :credentials
13
+ # enable :login_bypass # enables or disables login bypass in development mode, defaults to disable
14
+ # set :login_url, '/sign/in' # sets the utl to be redirected to if not logged in and in restricted area, defaults to '/login'
15
+ # disable :login_permissions # sets initial login permissions, defaults to { set_access(:*, :allow => :*, :with => :login) }
16
+ # disable :login_controller # disables default login controller to show an example of the custom one
17
+ #
18
+ # # required statement
19
+ # register Padrino::Login
20
+ # # example persistance storage
21
+ # enable :sessions
22
+ # end
23
+ #
24
+ # TODO: example controllers
25
+ #
26
+ module Login
27
+ class << self
28
+ def registered(app)
29
+ warn 'Padrino::Login must be registered before Padrino::Access' if app.respond_to?(:set_access)
30
+ included(app)
31
+ setup_storage(app)
32
+ setup_controller(app)
33
+ app.before do
34
+ log_in if authorization_required?
35
+ end
36
+ end
37
+
38
+ def included(base)
39
+ base.send(:include, InstanceMethods)
40
+ end
41
+
42
+ private
43
+
44
+ def setup_storage(app)
45
+ app.default(:session_key, "_login_#{app.app_name}")
46
+ app.default(:login_model, :account)
47
+ app.default(:credentials_accessor, :credentials)
48
+ app.send :attr_reader, app.credentials_accessor unless app.instance_methods.include?(app.credentials_accessor)
49
+ app.send :attr_writer, app.credentials_accessor unless app.instance_methods.include?(:"#{app.credentials_accessor}=")
50
+ app.default(:login_bypass, false)
51
+ end
52
+
53
+ def setup_controller(app)
54
+ app.default(:login_url, '/login')
55
+ app.default(:login_permissions) { set_access(:*, :allow => :*, :with => :login) }
56
+ app.default(:login_controller, true)
57
+ app.controller(:login) { include Controller } if app.login_controller
58
+ end
59
+ end
60
+
61
+ module InstanceMethods
62
+ # Returns the model used to authenticate visitors.
63
+ def login_model
64
+ @login_model ||= settings.login_model.to_s.classify.constantize
65
+ end
66
+
67
+ # Authenticates the visitor.
68
+ def authenticate
69
+ resource = login_model.authenticate(:email => params[:email], :password => params[:password])
70
+ resource ||= login_model.authenticate(:bypass => true) if settings.login_bypass && params[:bypass]
71
+ save_credentials(resource)
72
+ end
73
+
74
+ # Checks if the visitor is authenticated.
75
+ def logged_in?
76
+ !!(send(settings.credentials_accessor) || restore_credentials)
77
+ end
78
+
79
+ # Looks for authorization routine and calls it to check if the visitor is authorized.
80
+ def unauthorized?
81
+ respond_to?(:authorized?) && !authorized?
82
+ end
83
+
84
+ # Checks if the current location needs the visitor to be authorized.
85
+ def authorization_required?
86
+ if logged_in?
87
+ if unauthorized?
88
+ # 403 Forbidden, provided credentials were successfully
89
+ # authenticated but the credentials still do not grant
90
+ # the client permission to access the resource
91
+ error 403, '403 Forbidden'
92
+ else
93
+ false
94
+ end
95
+ else
96
+ unauthorized?
97
+ end
98
+ end
99
+
100
+ # Logs the visitor in using redirect to login page url.
101
+ def log_in
102
+ login_url = settings.login_url
103
+ if request.env['PATH_INFO'] != login_url
104
+ save_location
105
+ # 302 Found
106
+ redirect url(login_url)
107
+ # 401 Unauthorized, authentication is required and
108
+ # has not yet been provided
109
+ error 401, '401 Unauthorized'
110
+ end
111
+ end
112
+
113
+ # Saves credentials in session.
114
+ def save_credentials(resource)
115
+ session[settings.session_key] = resource.respond_to?(:id) ? resource.id : resource
116
+ send(:"#{settings.credentials_accessor}=", resource)
117
+ end
118
+
119
+ # Restores credentials from session using visitor model.
120
+ def restore_credentials
121
+ resource = login_model.authenticate(:id => session[settings.session_key])
122
+ send(:"#{settings.credentials_accessor}=", resource)
123
+ end
124
+
125
+ # Redirects back to saved location or '/'
126
+ def restore_location
127
+ redirect session.delete(:return_to) || url('/')
128
+ end
129
+
130
+ # Saves location to session for following redirect in case of successful authentication.
131
+ def save_location
132
+ uri = env['REQUEST_URI'] || url(env['PATH_INFO'])
133
+ return if uri.blank? || uri.match(/\.css$|\.js$|\.png$/)
134
+ session[:return_to] = "#{ENV['RACK_BASE_URI']}#{uri}"
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,20 @@
1
+ module Padrino
2
+ module Login
3
+ module Controller
4
+ def self.included(base)
5
+ base.get :index do
6
+ render :slim, :"new", :layout => "layout", :views => File.dirname(__FILE__)
7
+ end
8
+ base.post :index do
9
+ if authenticate
10
+ restore_location
11
+ else
12
+ params.delete 'password'
13
+ flash.now[:error] = 'Wrong password'
14
+ render :slim, :"new", :layout => "layout", :views => File.dirname(__FILE__)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ doctype html
2
+ html
3
+ head
4
+ meta charset="utf-8"
5
+ meta name="robots" content="noindex"
6
+ title Padrino::Login
7
+ link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css"
8
+ body
9
+ .container.login style='width: 287px'
10
+ = yield
@@ -0,0 +1,35 @@
1
+ h3
2
+ | Login
3
+ br
4
+ small= link_to request.env['HTTP_HOST'], url('/')
5
+
6
+ = form_tag( '', :class => 'form-horizontal well' ) do
7
+ - [:error, :warning, :success, :notice].each do |type|
8
+ - next if flash[type].blank?
9
+ .alert.alert-message class=('alert-' + (type == :notice ? :info : type).to_s) data-alert=true
10
+ = flash[type]
11
+
12
+ legend Social
13
+ .control-group
14
+ = link_to image_tag('/images/social/google.png'), url('/oauth/google')
15
+
16
+ legend Obsolete
17
+ .control-group
18
+ .input-prepend
19
+ span.add-on
20
+ i.icon-envelope
21
+ = text_field_tag :email, :value => params[:email], :placeholder => "email"
22
+ .control-group
23
+ .input-prepend
24
+ span.add-on
25
+ i.icon-lock
26
+ = password_field_tag :password, :value => params[:password], :placeholder => "password"
27
+ .control-group
28
+ = submit_tag('Log in', :class => 'btn btn-primary pull-right')
29
+ - if settings.login_bypass
30
+ label.checkbox
31
+ | Bypass
32
+ = check_box_tag :bypass, :value => 'Bypass'
33
+
34
+ small
35
+ = link_to 'Forgot password?', url('/login/reset_password')
@@ -0,0 +1,180 @@
1
+ module Padrino
2
+ ##
3
+ # Class to store and check permissions used in Padrino::Access.
4
+ #
5
+ class Permissions
6
+ ##
7
+ # Initializes new permissions storage.
8
+ #
9
+ # @example
10
+ # permissions = Permissions.new
11
+ #
12
+ def initialize
13
+ clear!
14
+ end
15
+
16
+ ##
17
+ # Clears permit records and action cache.
18
+ #
19
+ # @example
20
+ # permissions.clear!
21
+ #
22
+ def clear!
23
+ @permits = {}
24
+ @actions = {}
25
+ end
26
+
27
+ ##
28
+ # Adds a permission record to storage.
29
+ #
30
+ # @param [Symbol || Object] subject
31
+ # permit subject
32
+ # @param [Hash] options
33
+ # permit attributes
34
+ # @param [Symbol] options[:allow] || options[:action]
35
+ # what action to allow with objects
36
+ # @param [Symbol] options[:with] || options[:object]
37
+ # with what objects allow specified action
38
+ #
39
+ # @example
40
+ # permissions.add :robots, :allow => :protect, :object => :humans
41
+ # permissions.add @bender, :allow => :kill, :object => :humans
42
+ #
43
+ def add(*args)
44
+ @actions = {}
45
+ options = args.extract_options!
46
+ action, object = action_and_object(options)
47
+ object_type = detect_type(object)
48
+ args.each{ |subject| merge(subject, action, object_type) }
49
+ end
50
+
51
+ ##
52
+ # Checks if permission record exists. Returns a boolean or yield a block.
53
+ #
54
+ # @param [Object] subject
55
+ # performer of an action
56
+ # @param [Hash] options
57
+ # attributes to check
58
+ # @param [Symbol] options[:have]
59
+ # check if the subject has a role
60
+ # @param [Symbol] options[:allow] || options[:action]
61
+ # check if the subject is allowed to perform the action
62
+ # @param [Symbol] options[:with] || options[:object]
63
+ # check if the subject is allowed to interact with the subject
64
+ # @param [Proc]
65
+ # optional block to yield if the action is allowed
66
+ #
67
+ # @example
68
+ # # check if @bender have role :robots
69
+ # permissions.check @bender, :have => :robots # => true
70
+ # # check if @bender is allowed to kill :humans
71
+ # permissions.check @bender, :allow => :kill, :object => :humans # => true
72
+ # # check if @bender is allowed to kill :humans and yield a block
73
+ # permissions.check @bender, :allow => :kill, :object => :humans do
74
+ # @bender.kill_all! :humans
75
+ # end
76
+ #
77
+ def check(subject, options)
78
+ case
79
+ when options[:have]
80
+ check_role(subject, options[:have])
81
+ else
82
+ check_action(subject, *action_and_object(options))
83
+ end && (block_given? ? yield : true)
84
+ end
85
+
86
+ ##
87
+ # Populates and returns the list of objects available to the subject.
88
+ #
89
+ # @param [Object] subject
90
+ # the subject to be checked for actions
91
+ #
92
+ def find_objects(subject, target_action=nil)
93
+ find_actions(subject).inject([]) do |all,(action,objects)|
94
+ all |= objects if target_action.nil? || action == target_action || action == :*
95
+ all
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ # Merges a list of new permits into permissions storage.
102
+ def merge(subject, actions, object_type)
103
+ subject_id = detect_id(subject)
104
+ @permits[subject_id] ||= {}
105
+ Array(actions).each do |action|
106
+ @permits[subject_id][action] ||= []
107
+ @permits[subject_id][action] |= [object_type]
108
+ end
109
+ end
110
+
111
+ # Checks if the subject has the role.
112
+ def check_role(subject, roles)
113
+ if subject.respond_to?(:role)
114
+ Array(roles).include?(subject.role)
115
+ else
116
+ false
117
+ end
118
+ end
119
+
120
+ # Checks if the subject is allowed to perform the action with the object.
121
+ def check_action(subject, action, object)
122
+ actions = find_actions(subject)
123
+ objects = actions && (Array(actions[action]) | Array(actions[:*]))
124
+ objects && (objects & [:*, detect_type(object)]).any?
125
+ end
126
+
127
+ # Finds all permits for the subject. Caches the permits in @actions.
128
+ # find_actions(@bender) # => { :kill => { :humans }, :drink => { :booze }, :* => { :login } }
129
+ def find_actions(subject)
130
+ subject_id = detect_id(subject)
131
+ return @actions[subject_id] if @actions[subject_id]
132
+ actions = @permits[subject_id] || {}
133
+ if subject.respond_to?(:role) && (role_actions = @permits[subject.role.to_sym])
134
+ actions.merge!(role_actions){ |_,left,right| Array(left)|Array(right) }
135
+ end
136
+ if public_actions = @permits[:*]
137
+ actions.merge!(public_actions){ |_,left,right| Array(left)|Array(right) }
138
+ end
139
+ @actions[subject_id] = actions
140
+ end
141
+
142
+ # Returns object type.
143
+ # detect_type :humans # => :human
144
+ # detect_type 'foobar' # => 'foobar'
145
+ def detect_type(object)
146
+ case object
147
+ when Symbol
148
+ object.to_s.singularize.to_sym
149
+ else
150
+ object
151
+ end
152
+ end
153
+
154
+ # Returns parametrized subject.
155
+ # detect_id :robots # => :robots
156
+ # detect_id sluggable_ar_resource # => 'Sluggable-resource-slug'
157
+ # detect_id some_resource_with_id # => '4'
158
+ # detect_id generic_object # => "<Object:0x00001234>"
159
+ def detect_id(subject)
160
+ case
161
+ when Symbol === subject
162
+ subject
163
+ when subject.respond_to?(:to_param)
164
+ subject.to_param
165
+ when subject.respond_to?(:id)
166
+ subject.id.to_s
167
+ else
168
+ "#{subject}"
169
+ end
170
+ end
171
+
172
+ # Utility function to extract action and object from options. Defaults to [:*, :*]
173
+ # action_and_object(:allow => :kill, :object => :humans) # => [:kill, :humans]
174
+ # action_and_object(:action => :romance, :with => :mutants) # => [:romance, :mutants]
175
+ # action_and_object({}) # => [:*, :*]
176
+ def action_and_object(options)
177
+ [options[:allow] || options[:action] || :*, options[:with] || options[:object] || :*]
178
+ end
179
+ end
180
+ end