authorization_next 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Pavan Agrawal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # AuthorizationNext
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/authorization_next`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'authorization_next'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install authorization_next
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/authorization_next. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the AuthorizationNext project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/authorization/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "authorization_next/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "authorization_next"
9
+ spec.version = AuthorizationNext::VERSION
10
+ spec.authors = ["Pavan Agrawal"]
11
+ spec.email = ["pavan.agrawala@gmail.com"]
12
+
13
+ spec.summary = %q{Converted plugin to gem which will work with association as well.}
14
+ spec.description = %q{Converted plugin to gem which will work with association as well.}
15
+ spec.homepage = "https://github.com/pavanagrawal/authorization_next"
16
+ spec.license = "MIT"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.17"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "authorization_next"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,172 @@
1
+ require "authorization_next/version"
2
+ require File.dirname(__FILE__) + '/authorization_next/publishare/exceptions'
3
+ require File.dirname(__FILE__) + '/authorization_next/publishare/parser'
4
+
5
+
6
+
7
+ module AuthorizationNext
8
+ module Base
9
+
10
+ # Modify these constants in your environment.rb to tailor the plugin to your authentication system
11
+ if not Object.constants.include? "DEFAULT_REDIRECTION_HASH"
12
+ DEFAULT_REDIRECTION_HASH = { :controller => 'account', :action => 'login' }
13
+ end
14
+ if not Object.constants.include? "STORE_LOCATION_METHOD"
15
+ STORE_LOCATION_METHOD = :store_return_location
16
+ end
17
+
18
+ def self.included( recipient )
19
+ recipient.extend( ControllerClassMethods )
20
+ recipient.class_eval do
21
+ include ControllerInstanceMethods
22
+ end
23
+ end
24
+
25
+ module ControllerClassMethods
26
+
27
+ # Allow class-level authorization_next check.
28
+ # permit is used in a before_filter fashion and passes arguments to the before_filter.
29
+ def permit( authorization_expression, *args )
30
+ filter_keys = [ :only, :except ]
31
+ filter_args, eval_args = {}, {}
32
+ if args.last.is_a? Hash
33
+ filter_args.merge!( args.last.reject {|k,v| not filter_keys.include? k } )
34
+ eval_args.merge!( args.last.reject {|k,v| filter_keys.include? k } )
35
+ end
36
+ before_filter( filter_args ) do |controller|
37
+ controller.permit( authorization_expression, eval_args )
38
+ end
39
+ end
40
+ end
41
+
42
+ module ControllerInstanceMethods
43
+ include AuthorizationNext::Base::EvalParser # RecursiveDescentParser is another option
44
+
45
+ # Permit? turns off redirection by default and takes no blocks
46
+ def permit?( authorization_expression, *args )
47
+ @options = { :allow_guests => false, :redirect => false }
48
+ @options.merge!( args.last.is_a?( Hash ) ? args.last : {} )
49
+
50
+ has_permission?( authorization_expression )
51
+ end
52
+
53
+ # Allow method-level authorization_next checks.
54
+ # permit (without a question mark ending) calls redirect on denial by default.
55
+ # Specify :redirect => false to turn off redirection.
56
+ def permit( authorization_expression, *args )
57
+ @options = { :allow_guests => false, :redirect => true }
58
+ @options.merge!( args.last.is_a?( Hash ) ? args.last : {} )
59
+
60
+ if has_permission?( authorization_expression )
61
+ yield if block_given?
62
+ elsif @options[:redirect]
63
+ handle_redirection
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def has_permission?( authorization_expression )
70
+ @current_user = get_user
71
+ if not @options[:allow_guests]
72
+ if @current_user.nil? # We aren't logged in, or an exception has already been raised
73
+ return false
74
+ elsif not @current_user.respond_to? :id
75
+ raise( UserDoesntImplementID, "User doesn't implement #id")
76
+ elsif not @current_user.respond_to? :has_role?
77
+ raise( UserDoesntImplementRoles, "User doesn't implement #has_role?" )
78
+ end
79
+ end
80
+ parse_authorization_expression( authorization_expression )
81
+ end
82
+
83
+ # Handle redirection within permit if authorization is denied.
84
+ def handle_redirection
85
+ return if not self.respond_to?( :redirect_to )
86
+ redirection = DEFAULT_REDIRECTION_HASH
87
+ redirection[:controller] = @options[:redirect_controller] if @options[:redirect_controller]
88
+ redirection[:action] = @options[:redirect_action] if @options[:redirect_action]
89
+
90
+ # Store url in session for return if this is available from authentication
91
+ send( STORE_LOCATION_METHOD ) if respond_to? STORE_LOCATION_METHOD
92
+ if @current_user
93
+ flash[:notice] = "Permission denied. Your account cannot access the requested page."
94
+ else
95
+ flash[:notice] = @options[:redirect_message] ? @options[:redirect_message] : "Login is required"
96
+ end
97
+ redirect_to redirection
98
+ false # Want to short-circuit the filters
99
+ end
100
+
101
+ # Try to find current user by checking options hash and instance method in that order.
102
+ def get_user
103
+ if @options[:user]
104
+ @options[:user]
105
+ elsif @options[:get_user_method]
106
+ send( @options[:get_user_method] )
107
+ elsif self.respond_to? :current_user
108
+ current_user
109
+ elsif not @options[:allow_guests]
110
+ raise( CannotObtainUserObject, "Couldn't find #current_user or @user, and nothing appropriate found in hash" )
111
+ end
112
+ end
113
+
114
+ # Try to find a model to query for permissions
115
+ def get_model( str )
116
+ if str =~ /\s*([A-Z]+\w*)\s*/
117
+ # Handle model class
118
+ begin
119
+ Module.const_get( str )
120
+ rescue
121
+ raise CannotObtainModelClass, "Couldn't find model class: #{str}"
122
+ end
123
+ elsif str =~ /\s*:*(\w+)\s*/
124
+ # Handle model instances
125
+ model_name = $1
126
+ model_symbol = model_name.to_sym
127
+ if @options[model_symbol]
128
+ @options[model_symbol]
129
+ elsif instance_variables.include?( '@'+model_name )
130
+ instance_variable_get( '@'+model_name )
131
+ # Note -- while the following code makes autodiscovery more convenient, it's a little too much side effect & security question
132
+ # elsif self.params[:id]
133
+ # eval_str = model_name.camelize + ".find(#{self.params[:id]})"
134
+ # eval eval_str
135
+ else
136
+ raise CannotObtainModelObject, "Couldn't find model (#{str}) in hash or as an instance variable"
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ end
143
+ end
144
+
145
+
146
+ ActionController::Base.send( :include, AuthorizationNext::Base )
147
+ ActionView::Base.send( :include, AuthorizationNext::Base::ControllerInstanceMethods )
148
+
149
+ # You can perform authorization at varying degrees of complexity.
150
+ # Choose a style of authorization below (see README) and the appropriate
151
+ # mixin will be used for your app.
152
+
153
+ # When used with the auth_test app, we define this in config/environment.rb
154
+ # AUTHORIZATION_MIXIN = "hardwired"
155
+ if not Object.constants.include? "AUTHORIZATION_MIXIN"
156
+ AUTHORIZATION_MIXIN = "object roles"
157
+ end
158
+
159
+ case AUTHORIZATION_MIXIN
160
+ when "hardwired"
161
+ require "authorization_next/publishare/hardwired_roles"
162
+ ActiveRecord::Base.send( :include,
163
+ AuthorizationNext::HardwiredRoles::UserExtensions,
164
+ AuthorizationNext::HardwiredRoles::ModelExtensions
165
+ )
166
+ when "object roles"
167
+ require "authorization_next/publishare/object_roles_table"
168
+ ActiveRecord::Base.send( :include,
169
+ AuthorizationNext::ObjectRolesTable::UserExtensions,
170
+ AuthorizationNext::ObjectRolesTable::ModelExtensions
171
+ )
172
+ end
@@ -0,0 +1,40 @@
1
+ module AuthorizationNext #:nodoc:
2
+
3
+ # Base error class for AuthorizationNext module
4
+ class AuthorizationError < StandardError
5
+ end
6
+
7
+ # Raised when the authorization expression is invalid (cannot be parsed)
8
+ class AuthorizationExpressionInvalid < AuthorizationError
9
+ end
10
+
11
+ # Raised when we can't find the current user
12
+ class CannotObtainUserObject < AuthorizationError
13
+ end
14
+
15
+ # Raised when an authorization expression contains a model class that doesn't exist
16
+ class CannotObtainModelClass < AuthorizationError
17
+ end
18
+
19
+ # Raised when an authorization expression contains a model reference that doesn't exist
20
+ class CannotObtainModelObject < AuthorizationError
21
+ end
22
+
23
+ # Raised when the obtained user object doesn't implement #id
24
+ class UserDoesntImplementID < AuthorizationError
25
+ end
26
+
27
+ # Raised when the obtained user object doesn't implement #has_role?
28
+ class UserDoesntImplementRoles < AuthorizationError
29
+ end
30
+
31
+ # Raised when the obtained model doesn't implement #accepts_role?
32
+ class ModelDoesntImplementRoles < AuthorizationError
33
+ end
34
+
35
+ class CannotSetRoleWhenHardwired < AuthorizationError
36
+ end
37
+
38
+ class CannotSetObjectRoleWhenSimpleRoleTable < AuthorizationError
39
+ end
40
+ end
@@ -0,0 +1,81 @@
1
+ require File.dirname(__FILE__) + '/exceptions'
2
+
3
+ # In order to use this mixin, you'll need to define roles by overriding the
4
+ # following functions:
5
+ #
6
+ # User#has_role?(role)
7
+ # Return true or false depending on the roles (strings) passed in.
8
+ #
9
+ # Model#accepts_role?(role, user)
10
+ # Return true or false depending on the roles (strings) this particular user has for
11
+ # this particular model object.
12
+ #
13
+ # See http://www.writertopia.com/developers/authorization
14
+
15
+ module AuthorizationNext
16
+ module HardwiredRoles
17
+
18
+ module UserExtensions
19
+ def self.included( recipient )
20
+ recipient.extend( ClassMethods )
21
+ end
22
+
23
+ module ClassMethods
24
+ def acts_as_authorized_user
25
+ include AuthorizationNext::HardwiredRoles::UserExtensions::InstanceMethods
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ # If roles aren't explicitly defined in user class then return false
31
+ def has_role?( role, authorizable_object = nil )
32
+ false
33
+ end
34
+
35
+ def has_role( role, authorizable_object = nil )
36
+ raise( CannotSetRoleWhenHardwired,
37
+ "Hardwired mixin: Cannot set user to role #{role}. Don't use #has_role, use code in models."
38
+ )
39
+ end
40
+
41
+ def has_no_role( role, authorizable_object = nil )
42
+ raise( CannotSetRoleWhenHardwired,
43
+ "Hardwired mixin: Cannot remove user role #{role}. Don't use #has_no_role, use code in models."
44
+ )
45
+ end
46
+ end
47
+ end
48
+
49
+ module ModelExtensions
50
+ def self.included( recipient )
51
+ recipient.extend( ClassMethods )
52
+ end
53
+
54
+ module ClassMethods
55
+ def acts_as_authorizable
56
+ include AuthorizationNext::HardwiredRoles::ModelExtensions::InstanceMethods
57
+ end
58
+ end
59
+
60
+ module InstanceMethods
61
+ def accepts_role?( role, user )
62
+ return false
63
+ end
64
+
65
+ def accepts_role( role, user )
66
+ raise( CannotSetRoleWhenHardwired,
67
+ "Hardwired mixin: Cannot set user to role #{role}. Don't use #accepts_role, use code in models."
68
+ )
69
+ end
70
+
71
+ def accepts_no_role( role, user )
72
+ raise( CannotSetRoleWhenHardwired,
73
+ "Hardwired mixin: Cannot set user to role #{role}. Don't use #accepts_no_role, use code in models."
74
+ )
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+
@@ -0,0 +1,125 @@
1
+ require File.dirname(__FILE__) + '/exceptions'
2
+
3
+ # Provides the appearance of dynamically generated methods on the roles database.
4
+ #
5
+ # Examples:
6
+ # user.is_member? --> Returns true if user has any role of "member"
7
+ # user.is_member_of? this_workshop --> Returns true/false. Must have authorizable object after query.
8
+ # user.is_eligible_for [this_award] --> Gives user the role "eligible" for "this_award"
9
+ # user.is_moderator --> Gives user the general role "moderator" (not tied to any class or object)
10
+ # user.is_candidate_of_what --> Returns array of objects for which this user is a "candidate"
11
+ #
12
+ # model.has_members --> Returns array of users which have role "member" on that model
13
+ # model.has_members? --> Returns true/false
14
+ #
15
+ module AuthorizationNext
16
+ module Identity
17
+
18
+ module UserExtensions
19
+ module InstanceMethods
20
+
21
+ def method_missing( method_sym, *args )
22
+ method_name = method_sym.to_s
23
+ authorizable_object = args.empty? ? nil : args[0]
24
+
25
+ base_regex = "is_(\\w+)"
26
+ fancy_regex = base_regex + "_(#{AuthorizationNext::Base::VALID_PREPOSITIONS_PATTERN})"
27
+ is_either_regex = '^((' + fancy_regex + ')|(' + base_regex + '))'
28
+ base_not_regex = "is_no[t]?_(\\w+)"
29
+ fancy_not_regex = base_not_regex + "_(#{AuthorizationNext::Base::VALID_PREPOSITIONS_PATTERN})"
30
+ is_not_either_regex = '^((' + fancy_not_regex + ')|(' + base_not_regex + '))'
31
+
32
+ if method_name =~ Regexp.new(is_either_regex + '_what$')
33
+ role_name = $3 || $6
34
+ has_role_for_objects(role_name)
35
+ elsif method_name =~ Regexp.new(is_not_either_regex + '\?$')
36
+ role_name = $3 || $6
37
+ not is_role?( role_name, authorizable_object )
38
+ elsif method_name =~ Regexp.new(is_either_regex + '\?$')
39
+ role_name = $3 || $6
40
+ is_role?( role_name, authorizable_object )
41
+ elsif method_name =~ Regexp.new(is_not_either_regex + '$')
42
+ role_name = $3 || $6
43
+ is_no_role( role_name, authorizable_object )
44
+ elsif method_name =~ Regexp.new(is_either_regex + '$')
45
+ role_name = $3 || $6
46
+ is_role( role_name, authorizable_object )
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def is_role?( role_name, authorizable_object )
55
+ if authorizable_object.nil?
56
+ return self.has_role?(role_name)
57
+ elsif authorizable_object.respond_to?(:accepts_role?)
58
+ return self.has_role?(role_name, authorizable_object)
59
+ end
60
+ false
61
+ end
62
+
63
+ def is_no_role( role_name, authorizable_object = nil )
64
+ if authorizable_object.nil?
65
+ self.has_no_role role_name
66
+ else
67
+ self.has_no_role role_name, authorizable_object
68
+ end
69
+ end
70
+
71
+ def is_role( role_name, authorizable_object = nil )
72
+ if authorizable_object.nil?
73
+ self.has_role role_name
74
+ else
75
+ self.has_role role_name, authorizable_object
76
+ end
77
+ end
78
+
79
+ def has_role_for_objects(role_name)
80
+ roles = self.roles.find_all_by_name( role_name )
81
+ roles.collect do |role|
82
+ if role.authorizable_id.nil?
83
+ role.authorizable_type.nil? ?
84
+ nil : Module.const_get( role.authorizable_type ) # Returns class
85
+ else
86
+ role.authorizable
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ module ModelExtensions
94
+ module InstanceMethods
95
+
96
+ def method_missing( method_sym, *args )
97
+ method_name = method_sym.to_s
98
+ if method_name =~ /^has_(\w+)\?$/
99
+ role_name = $1.singularize
100
+ self.accepted_roles.find_all_by_name(role_name).any? { |role| role.users }
101
+ elsif method_name =~ /^has_(\w+)$/
102
+ role_name = $1.singularize
103
+ users = self.accepted_roles.find_all_by_name(role_name).collect { |role| role.users }
104
+ users.flatten.uniq if users
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def respond_to? method_sym
111
+ method_name = method_sym.to_s
112
+ if method_name =~ /^has_(\w+)\?$/
113
+ true
114
+ elsif method_name =~ /^has_(\w+)$/
115
+ true
116
+ else
117
+ super
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+ end