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