padrino-auth 0.0.12

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