accessly 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ require "accessly/base"
2
+
3
+ module Accessly
4
+ # Accessly::Query is the interface that hides the implementation
5
+ # of the data layer. Ask Accessly::Query whether an actor
6
+ # has permission on a record, ask it for a list of permitted records for the record
7
+ # type, and ask it whether an actor has a general permission not
8
+ # related to any certain record or record type.
9
+ class Query < Base
10
+
11
+ # Create an instance of Accessly::Query.
12
+ # Lookups are cached in inherited object(s) to prevent redundant calls to the database.
13
+ # Pass in a Hash or ActiveRecord::Base for actors if the actor(s)
14
+ # inherit some permissions from other actors in the system. This may happen
15
+ # when you have a user in one or more groups or organizations with their own
16
+ # access control permissions.
17
+ #
18
+ # @param actors [Hash, ActiveRecord::Base] The actor(s) we're checking permission(s)
19
+ #
20
+ # @example
21
+ # # Create a new object with a single actor
22
+ # Accessly::Query.new(user)
23
+ # @example
24
+ # # Create a new object with multiple actors
25
+ # Accessly::Query.new(User => user.id, Group => [1,2], Organization => Organization.where(user_id: user.id).pluck(:id))
26
+ def initialize(actors)
27
+ super(actors)
28
+ end
29
+
30
+ # Check whether an actor has a given permission.
31
+ # @return [Boolean]
32
+ # @overload can?(action_id, namespace)
33
+ # Ask whether the actor has permission to perform action_id
34
+ # in the given namespace. Multiple actions can have the same id
35
+ # as long as their namespace is different. The namespace can be
36
+ # any String. We recommend using namespace to group a class of
37
+ # permissions, such as to group parts of a particular feature
38
+ # in your application.
39
+ #
40
+ # @param action_id [Integer, Array<Integer>] The action or actions we're checking whether the actor has. If this is an array, then the check is ORed.
41
+ # @param namespace [String] The namespace of the given action_id.
42
+ # @return [Boolean] Returns true if actor has been granted the permission, false otherwise.
43
+ #
44
+ # @example
45
+ # # Can the user perform the action with id 3 for posts?
46
+ # Accessly.can?(user, 3, "posts")
47
+ # @example
48
+ # # Can the user perform the action with id 5 for Posts?
49
+ # Accessly::Query.new(user).can?(5, Post)
50
+ # @example
51
+ # # Can the sets of actors perform the action with id 5 for Posts?
52
+ # Accessly::Query.new(User => user.id, Group => [1,2]).can?(5, Post)
53
+ # @example
54
+ # # Can the user on segment 1 perform the action with id 5 for Posts
55
+ # Accessly::Query.new(user).on_segment(1).can?(5, Post)
56
+ # @example
57
+ # # Can the sets of actors on segment 1 perform the action with id 5 for Posts
58
+ # Accessly::Query.new(User => user.id, Group => [1,2]).on_segment(1).can?(5, Post)
59
+ #
60
+ # @overload can?(action_id, object_type, object_id)
61
+ # Ask whether the actor has permission to perform action_id
62
+ # on a given record.
63
+ #
64
+ # @param action_id [Integer, Array<Integer>] The action or actions we're checking whether the actor has. If this is an array, then the check is ORed.
65
+ # @param object_type [ActiveRecord::Base] The ActiveRecord model which we're checking for permission on.
66
+ # @param object_id [Integer] The id of the ActiveRecord object which we're checking for permission on.
67
+ # @return [Boolean] Returns true if actor has been granted the permission on the specified record, false otherwise.
68
+ #
69
+ # @example
70
+ # # Can the user perform the action with id 5 for the Post with id 7?
71
+ # Accessly::Query.new(user).can?(5, Post, 7)
72
+ # @example
73
+ # # Can the sets of actors perform the action with id 5 for the Post with id 7?
74
+ # Accessly::Query.new(User => user.id, Group => [1,2]).can?(5, Post, 7)
75
+ # @example
76
+ # # Can the user on segment 1 perform the action with id 5 for the Post with id 7?
77
+ # Accessly::Query.new(user).on_segment(1).can?(5, Post, 7)
78
+ # @example
79
+ # # Can the sets of actors on segment 1 perform the action with id 5 for the Post with id 7?
80
+ # Accessly::Query.new(User => user.id, Group => [1,2]).on_segment(1).can?(5, Post, 7)
81
+ def can?(action_id, object_type, object_id = nil)
82
+ if object_id.nil?
83
+ permitted_action_query.can?(action_id, object_type)
84
+ else
85
+ permitted_action_on_object_query.can?(action_id, object_type, object_id)
86
+ end
87
+ end
88
+
89
+ # Returns an ActiveRecord::Relation of ids in the namespace for
90
+ # which the actor has permission to perform action_id.
91
+ #
92
+ # @param action_id [Integer] The action we're checking on the actor in the namespace.
93
+ # @param namespace [String] The namespace to check actor permissions.
94
+ # @return [ActiveRecord::Relation]
95
+ #
96
+ # @example
97
+ # # Give me the list of Post ids on which the user has permission to perform action_id 3
98
+ # Accessly::Query.new(user).list(3, Post)
99
+ # @example
100
+ # # Give me the list of Post ids on which the user has permission to perform action_id 3 on segment 1
101
+ # Accessly::Query.new(user).on_segment(1).list(3, Post)
102
+ # @example
103
+ # # Give me the list of Post ids on which the user and its groups has permission to perform action_id 3
104
+ # Accessly::Query.new(User => user.id, Group => [1,2]).list(3, Post)
105
+ # @example
106
+ # # Give me the list of Post ids on which the user and its groups has permission to perform action_id 3 on segment 1
107
+ # Accessly::Query.new(User => user.id, Group => [1,2]).on_segment(1).list(3, Post)
108
+ def list(action_id, namespace)
109
+ permitted_action_on_object_query.list(action_id, namespace)
110
+ end
111
+
112
+ private
113
+
114
+ def permitted_action_query
115
+ @_permitted_action_query ||= Accessly::PermittedActions::Query.new(@actors, @segment_id)
116
+ end
117
+
118
+ def permitted_action_on_object_query
119
+ @_permitted_action_on_object_query ||= Accessly::PermittedActions::OnObjectQuery.new(@actors, @segment_id)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,24 @@
1
+ module Accessly
2
+ class QueryBuilder
3
+
4
+ # Builds a query with a series of actors 'OR' together
5
+ #
6
+ # Use like this:
7
+ # `Accessly::QueryBuilder.with_actors(PermittedActionOnObject, {User => 1, Group => [2,3]})`
8
+ #
9
+ # @param query [ActiveRecord::Relation] The relation on which to append the where clause
10
+ # @param actors [Hash] A hash of actors where the key is the object/classname and the value is an Integer or array of Integers
11
+ # @return [ActiveRecord::Relation]
12
+ def self.with_actors(query, actors)
13
+ result_query = nil
14
+ actors.each do |key, value|
15
+ result_query = if result_query.nil?
16
+ query.where(actor_type: String(key), actor_id: value)
17
+ else
18
+ result_query.or(query.where(actor_type: String(key), actor_id: value))
19
+ end
20
+ end
21
+ result_query
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Accessly
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Install Accessly components inside a rails project
3
+
4
+ Example:
5
+ rails generate accessly:install
@@ -0,0 +1,51 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module Accessly
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ source_root File.expand_path("../templates", __FILE__)
9
+
10
+ def create_accessly_migration
11
+ copy_migration "create_permitted_actions.rb"
12
+ copy_migration "create_permitted_action_on_objects.rb"
13
+ end
14
+
15
+ private
16
+
17
+ def copy_migration(migration_name, config = {})
18
+ unless migration_exists?(migration_name)
19
+ migration_template(
20
+ "db/migrate/#{migration_name}",
21
+ "db/migrate/#{migration_name}",
22
+ config.merge(migration_version: migration_version),
23
+ )
24
+ end
25
+ end
26
+
27
+ def migration_exists?(name)
28
+ existing_migrations.include?(name)
29
+ end
30
+
31
+ def existing_migrations
32
+ @existing_migrations ||= Dir.glob("db/migrate/*.rb").map do |file|
33
+ migration_name_without_timestamp(file)
34
+ end
35
+ end
36
+
37
+ def migration_name_without_timestamp(file)
38
+ file.sub(%r{^.*(db/migrate/)(?:\d+_)?}, '')
39
+ end
40
+
41
+ # necessary to generate timestamps when using `create_migration`
42
+ def self.next_migration_number(dir)
43
+ ActiveRecord::Generators::Base.next_migration_number(dir)
44
+ end
45
+
46
+ def migration_version
47
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ class CreatePermittedActionOnObjects < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :accessly_permitted_action_on_objects, force: true, id: :uuid do |t|
4
+ t.column :segment_id, :integer, default: -1
5
+ t.column :action, :integer, null: false
6
+ t.column :actor_id, :integer, null: false
7
+ t.column :actor_type, :string, null: false
8
+ t.column :object_type, :string, null: false
9
+ t.column :object_id, :integer, null: false
10
+ end
11
+
12
+ add_index(:accessly_permitted_action_on_objects, [:segment_id, :actor_type, :actor_id, :object_type, :object_id, :action], unique: true, name: "acessly_paoo_uniq_table_idx")
13
+ add_index(:accessly_permitted_action_on_objects, [:segment_id, :object_type, :object_id, :action], name: "acessly_paoo_on_object_idx")
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ class CreatePermittedActions < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :accessly_permitted_actions, force: true, id: :uuid do |t|
4
+ t.column :segment_id, :integer, default: -1
5
+ t.column :action, :integer, null: false
6
+ t.column :actor_id, :integer, null: false
7
+ t.column :actor_type, :string, null: false
8
+ t.column :object_type, :string, null: false
9
+ end
10
+
11
+ add_index(:accessly_permitted_actions, [:segment_id, :actor_type, :actor_id, :object_type, :action], unique: true, name: "acessly_pa_uniq_table_idx")
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: accessly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Milam
8
+ - Eddie Hourigan
9
+ - Ross Reinhardt
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2018-03-28 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '5.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '5.0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: bundler
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.16'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.16'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rake
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '10.0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '10.0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: minitest
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '5.0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '5.0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: database_cleaner
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '1.5'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '1.5'
85
+ - !ruby/object:Gem::Dependency
86
+ name: sqlite3
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '1.3'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: '1.3'
99
+ - !ruby/object:Gem::Dependency
100
+ name: rails
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: '5.0'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '5.0'
113
+ description: Use the policy pattern to define access control mechanisms in Rails.
114
+ Store user-level, group-level, or org-level permission on any given record or concept
115
+ in the database with ultra-fast lookups.
116
+ email:
117
+ - devops@lessonly.com
118
+ executables: []
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - ".editorconfig"
123
+ - ".gitignore"
124
+ - ".ruby-version"
125
+ - ".tool-versions"
126
+ - Gemfile
127
+ - Gemfile.lock
128
+ - LICENSE
129
+ - LICENSE.txt
130
+ - README.md
131
+ - Rakefile
132
+ - accessly.gemspec
133
+ - bin/console
134
+ - bin/setup
135
+ - lib/accessly.rb
136
+ - lib/accessly/base.rb
137
+ - lib/accessly/models/permitted_action.rb
138
+ - lib/accessly/models/permitted_action_on_object.rb
139
+ - lib/accessly/permission/grant.rb
140
+ - lib/accessly/permission/revoke.rb
141
+ - lib/accessly/permitted_actions/base.rb
142
+ - lib/accessly/permitted_actions/on_object_query.rb
143
+ - lib/accessly/permitted_actions/query.rb
144
+ - lib/accessly/policy/base.rb
145
+ - lib/accessly/query.rb
146
+ - lib/accessly/query_builder.rb
147
+ - lib/accessly/version.rb
148
+ - lib/generators/accessly/install/USAGE
149
+ - lib/generators/accessly/install/install_generator.rb
150
+ - lib/generators/accessly/install/templates/db/migrate/create_permitted_action_on_objects.rb
151
+ - lib/generators/accessly/install/templates/db/migrate/create_permitted_actions.rb
152
+ homepage: https://github.com/lessonly/accessly
153
+ licenses:
154
+ - MIT
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.6.13
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: Simplified access control in Rails
176
+ test_files: []