action_policy 0.4.4 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +203 -174
  3. data/README.md +5 -4
  4. data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +62 -0
  5. data/lib/.rbnext/2.7/action_policy/i18n.rb +56 -0
  6. data/lib/.rbnext/2.7/action_policy/policy/cache.rb +101 -0
  7. data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +162 -0
  8. data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +89 -0
  9. data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +124 -0
  10. data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +159 -0
  11. data/lib/.rbnext/3.0/action_policy/behaviour.rb +115 -0
  12. data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +62 -0
  13. data/lib/.rbnext/3.0/action_policy/behaviours/scoping.rb +35 -0
  14. data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +59 -0
  15. data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +72 -0
  16. data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +69 -0
  17. data/lib/.rbnext/3.0/action_policy/policy/authorization.rb +87 -0
  18. data/lib/.rbnext/3.0/action_policy/policy/cache.rb +101 -0
  19. data/lib/.rbnext/3.0/action_policy/policy/core.rb +161 -0
  20. data/lib/.rbnext/3.0/action_policy/policy/defaults.rb +31 -0
  21. data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +37 -0
  22. data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +162 -0
  23. data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +212 -0
  24. data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +160 -0
  25. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +89 -0
  26. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +124 -0
  27. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +159 -0
  28. data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +19 -0
  29. data/lib/action_policy.rb +7 -1
  30. data/lib/action_policy/behaviour.rb +22 -16
  31. data/lib/action_policy/behaviours/policy_for.rb +10 -3
  32. data/lib/action_policy/behaviours/scoping.rb +2 -1
  33. data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
  34. data/lib/action_policy/ext/module_namespace.rb +1 -6
  35. data/lib/action_policy/ext/policy_cache_key.rb +10 -30
  36. data/lib/action_policy/i18n.rb +1 -1
  37. data/lib/action_policy/lookup_chain.rb +29 -15
  38. data/lib/action_policy/policy/aliases.rb +7 -12
  39. data/lib/action_policy/policy/authorization.rb +8 -7
  40. data/lib/action_policy/policy/cache.rb +11 -17
  41. data/lib/action_policy/policy/core.rb +25 -12
  42. data/lib/action_policy/policy/defaults.rb +3 -9
  43. data/lib/action_policy/policy/execution_result.rb +3 -9
  44. data/lib/action_policy/policy/pre_check.rb +19 -58
  45. data/lib/action_policy/policy/reasons.rb +31 -19
  46. data/lib/action_policy/policy/scoping.rb +5 -6
  47. data/lib/action_policy/rails/controller.rb +6 -1
  48. data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
  49. data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
  50. data/lib/action_policy/rspec/dsl.rb +1 -1
  51. data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
  52. data/lib/action_policy/utils/pretty_print.rb +21 -24
  53. data/lib/action_policy/utils/suggest_message.rb +1 -3
  54. data/lib/action_policy/version.rb +1 -1
  55. data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
  56. data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
  57. data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  58. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  59. data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
  60. metadata +54 -119
  61. data/.gitattributes +0 -2
  62. data/.github/ISSUE_TEMPLATE.md +0 -21
  63. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  64. data/.github/bug_report_template.rb +0 -175
  65. data/.gitignore +0 -15
  66. data/.rubocop.yml +0 -54
  67. data/.tidelift.yml +0 -6
  68. data/.travis.yml +0 -31
  69. data/Gemfile +0 -22
  70. data/Rakefile +0 -27
  71. data/action_policy.gemspec +0 -44
  72. data/benchmarks/namespaced_lookup_cache.rb +0 -74
  73. data/benchmarks/pre_checks.rb +0 -73
  74. data/bin/console +0 -14
  75. data/bin/setup +0 -8
  76. data/docs/.nojekyll +0 -0
  77. data/docs/CNAME +0 -1
  78. data/docs/README.md +0 -79
  79. data/docs/_sidebar.md +0 -27
  80. data/docs/aliases.md +0 -122
  81. data/docs/assets/docsify-search.js +0 -364
  82. data/docs/assets/docsify.min.js +0 -3
  83. data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
  84. data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
  85. data/docs/assets/images/banner.png +0 -0
  86. data/docs/assets/images/cache.png +0 -0
  87. data/docs/assets/images/cache.svg +0 -70
  88. data/docs/assets/images/layer.png +0 -0
  89. data/docs/assets/images/layer.svg +0 -35
  90. data/docs/assets/prism-ruby.min.js +0 -1
  91. data/docs/assets/styles.css +0 -347
  92. data/docs/assets/vue.min.css +0 -1
  93. data/docs/authorization_context.md +0 -92
  94. data/docs/behaviour.md +0 -113
  95. data/docs/caching.md +0 -291
  96. data/docs/controller_action_aliases.md +0 -109
  97. data/docs/custom_lookup_chain.md +0 -48
  98. data/docs/custom_policy.md +0 -53
  99. data/docs/debugging.md +0 -55
  100. data/docs/decorators.md +0 -27
  101. data/docs/favicon.ico +0 -0
  102. data/docs/graphql.md +0 -302
  103. data/docs/i18n.md +0 -44
  104. data/docs/index.html +0 -43
  105. data/docs/instrumentation.md +0 -84
  106. data/docs/lookup_chain.md +0 -22
  107. data/docs/namespaces.md +0 -77
  108. data/docs/non_rails.md +0 -28
  109. data/docs/pre_checks.md +0 -57
  110. data/docs/pundit_migration.md +0 -80
  111. data/docs/quick_start.md +0 -118
  112. data/docs/rails.md +0 -120
  113. data/docs/reasons.md +0 -120
  114. data/docs/scoping.md +0 -255
  115. data/docs/testing.md +0 -390
  116. data/docs/writing_policies.md +0 -107
  117. data/gemfiles/jruby.gemfile +0 -8
  118. data/gemfiles/rails42.gemfile +0 -9
  119. data/gemfiles/rails6.gemfile +0 -8
  120. data/gemfiles/railsmaster.gemfile +0 -6
  121. data/lib/action_policy/ext/string_match.rb +0 -14
  122. data/lib/action_policy/ext/yield_self_then.rb +0 -25
data/.gitignore DELETED
@@ -1,15 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- Gemfile.local
11
-
12
- spec/dummy/log/*
13
- spec/dummy/db/*.sqlite3
14
- spec/dummy/db/*.sqlite3-journal
15
- spec/dummy/tmp/
@@ -1,54 +0,0 @@
1
- require:
2
- - standard/cop/semantic_blocks
3
- - rubocop-md
4
-
5
- inherit_gem:
6
- standard: config/base.yml
7
-
8
- AllCops:
9
- Exclude:
10
- - 'bin/*'
11
- - 'tmp/**/*'
12
- - 'Gemfile'
13
- - 'vendor/**/*'
14
- - 'gemfiles/**/*'
15
- - 'lib/generators/**/templates/**/*'
16
- DisplayCopNames: true
17
- TargetRubyVersion: 2.4
18
-
19
- Standard/SemanticBlocks:
20
- Enabled: false
21
-
22
- Style/FrozenStringLiteralComment:
23
- Enabled: true
24
-
25
- Style/TrailingCommaInArrayLiteral:
26
- EnforcedStyleForMultiline: no_comma
27
-
28
- Style/TrailingCommaInHashLiteral:
29
- EnforcedStyleForMultiline: no_comma
30
-
31
- Layout/AlignParameters:
32
- EnforcedStyle: with_first_parameter
33
-
34
- Lint/Void:
35
- Exclude:
36
- - '**/*.md'
37
-
38
- # See https://github.com/rubocop-hq/rubocop/issues/4222
39
- Lint/AmbiguousBlockAssociation:
40
- Exclude:
41
- - 'spec/**/*'
42
- - '**/*.md'
43
-
44
- Lint/DuplicateMethods:
45
- Exclude:
46
- - '**/*.md'
47
-
48
- Naming/FileName:
49
- Exclude:
50
- - '**/*.md'
51
-
52
- Layout/InitialIndentation:
53
- Exclude:
54
- - 'CHANGELOG.md'
@@ -1,6 +0,0 @@
1
- ci:
2
- type:
3
- development:
4
- tests:
5
- unlicensed: skip
6
- outdated: skip
@@ -1,31 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- notifications:
5
- email: false
6
-
7
- before_install:
8
- - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
9
- - gem install bundler -v '< 2'
10
-
11
- script:
12
- - bundle exec rake test:ci
13
-
14
- matrix:
15
- fast_finish: true
16
- include:
17
- - rvm: ruby-head
18
- gemfile: gemfiles/railsmaster.gemfile
19
- - rvm: jruby-9.2.8.0
20
- gemfile: gemfiles/jruby.gemfile
21
- - rvm: 2.6.5
22
- gemfile: gemfiles/rails6.gemfile
23
- - rvm: 2.5.3
24
- gemfile: Gemfile
25
- - rvm: 2.5.3
26
- gemfile: gemfiles/rails42.gemfile
27
- allow_failures:
28
- - rvm: ruby-head
29
- gemfile: gemfiles/railsmaster.gemfile
30
- - rvm: jruby-9.2.8.0
31
- gemfile: gemfiles/jruby.gemfile
data/Gemfile DELETED
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
7
- gem "pry-byebug", platform: :mri
8
-
9
- gem "method_source"
10
- gem "unparser"
11
-
12
- gem 'sqlite3', "~> 1.3.0", platform: :mri
13
- gem 'activerecord-jdbcsqlite3-adapter', '~> 50.0', platform: :jruby
14
- gem 'jdbc-sqlite3', platform: :jruby
15
-
16
- local_gemfile = File.join(__dir__, "Gemfile.local")
17
-
18
- if File.exist?(local_gemfile)
19
- eval(File.read(local_gemfile)) # rubocop:disable Security/Eval
20
- else
21
- gem "rails", "~> 5.0"
22
- end
data/Rakefile DELETED
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rake/testtask"
4
- require "rubocop/rake_task"
5
- require "rspec/core/rake_task"
6
-
7
- RuboCop::RakeTask.new
8
-
9
- RSpec::Core::RakeTask.new(:spec)
10
-
11
- Rake::TestTask.new(:test) do |t|
12
- t.libs << "test"
13
- t.libs << "lib"
14
- t.test_files = FileList["test/**/*_test.rb"]
15
- end
16
-
17
- namespace :test do
18
- task :isolated do
19
- Dir.glob("test/**/*_test.rb").all? do |file|
20
- sh(Gem.ruby, "-w", "-Ilib:test", file)
21
- end || raise("Failures")
22
- end
23
-
24
- task ci: %w[rubocop test:isolated spec]
25
- end
26
-
27
- task default: [:rubocop, :test, :spec]
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path("lib", __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require "action_policy/version"
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = "action_policy"
9
- spec.version = ActionPolicy::VERSION
10
- spec.authors = ["Vladimir Dementyev"]
11
- spec.email = ["dementiev.vm@gmail.com"]
12
-
13
- spec.summary = "Authorization framework for Ruby/Rails application"
14
- spec.description = "Authorization framework for Ruby/Rails application"
15
- spec.homepage = "https://github.com/palkan/action_policy"
16
- spec.license = "MIT"
17
-
18
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
- f.match(%r{^(test|spec|features)/})
20
- end
21
-
22
- spec.metadata = {
23
- "bug_tracker_uri" => "http://github.com/palkan/action_policy/issues",
24
- "changelog_uri" => "https://github.com/palkan/action_policy/blob/master/CHANGELOG.md",
25
- "documentation_uri" => "https://actionpolicy.evilmartians.io/",
26
- "homepage_uri" => "https://actionpolicy.evilmartians.io/",
27
- "source_code_uri" => "http://github.com/palkan/action_policy"
28
- }
29
-
30
- spec.require_paths = ["lib"]
31
-
32
- spec.required_ruby_version = ">= 2.4.0"
33
-
34
- spec.add_development_dependency "ammeter", "~> 1.1.3"
35
- spec.add_development_dependency "bundler", ">= 1.15"
36
- spec.add_development_dependency "minitest", "~> 5.0"
37
- spec.add_development_dependency "rake", "~> 10.0"
38
- spec.add_development_dependency "rspec", "~> 3.3"
39
- spec.add_development_dependency "rubocop", "~> 0.67.0"
40
- spec.add_development_dependency "rubocop-md", "~> 0.2"
41
- spec.add_development_dependency "standard", "~> 0.0.39"
42
- spec.add_development_dependency "benchmark-ips", "~> 2.7.0"
43
- spec.add_development_dependency "i18n"
44
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # This benchmark measures the efficiency of NamespaceCache.
5
- #
6
- # Run it multiple times with cache on/off to see the results:
7
- #
8
- # $ bundle exec ruby namespaced_lookup_cache.rb
9
- # $ bundle exec ruby namespaced_lookup_cache.rb
10
- # $ NO_CACHE=1 bundle exec ruby namespaced_lookup_cache.rb
11
- # $ NO_CACHE=1 bundle exec ruby namespaced_lookup_cache.rb
12
- #
13
-
14
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
15
-
16
- require "action_policy"
17
- require "benchmark/ips"
18
-
19
- GC.disable
20
-
21
- class A; end
22
-
23
- class B; end
24
-
25
- module X
26
- class BPolicy < ActionPolicy::Base; end
27
-
28
- module Y
29
- module Z
30
- class APolicy < ActionPolicy::Base; end
31
- end
32
- end
33
- end
34
-
35
- a = A.new
36
- b = B.new
37
-
38
- if ENV["NO_CACHE"]
39
- ActionPolicy::LookupChain.namespace_cache_enabled = false
40
- end
41
-
42
- results_path = File.join(__dir__, "../tmp/#{__FILE__.sub(/\.rb$/, ".txt")}")
43
- FileUtils.mkdir_p File.dirname(results_path)
44
-
45
- Benchmark.ips do |x|
46
- x.warmup = 0
47
-
48
- x.report("cache A") do
49
- ActionPolicy.lookup(a, namespace: X::Y::Z)
50
- end
51
-
52
- x.report("cache B") do
53
- ActionPolicy.lookup(b, namespace: X::Y::Z)
54
- end
55
-
56
- x.report("no cache A") do
57
- ActionPolicy.lookup(a, namespace: X::Y::Z)
58
- end
59
-
60
- x.report("no cache B") do
61
- ActionPolicy.lookup(b, namespace: X::Y::Z)
62
- end
63
-
64
- x.hold! results_path
65
-
66
- x.compare!
67
- end
68
-
69
- #
70
- # Comparison:
71
- # cache A: 204788.3 i/s
72
- # cache B: 202536.9 i/s - same-ish: difference falls within error
73
- # no cache A: 127772.8 i/s - same-ish: difference falls within error
74
- # no cache B: 63487.2 i/s - 3.23x slower
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This benchmark measures the difference between catch/throw and jump-less implementation.
4
-
5
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
-
7
- require "action_policy"
8
- require "benchmark/ips"
9
-
10
- GC.disable
11
-
12
- class A; end
13
-
14
- class B; end
15
-
16
- class APolicy < ActionPolicy::Base
17
- authorize :user, optional: true
18
-
19
- pre_check :do_nothing, :deny_all
20
-
21
- def show?
22
- true
23
- end
24
-
25
- def do_nothing
26
- end
27
-
28
- def deny_all
29
- deny!
30
- end
31
- end
32
-
33
- class BPolicy < APolicy
34
- def run_pre_checks(rule)
35
- catch :policy_fulfilled do
36
- self.class.pre_checks.each do |check|
37
- next unless check.applicable?(rule)
38
- check.call(self)
39
- end
40
-
41
- return yield if block_given?
42
- end
43
-
44
- result.value
45
- end
46
-
47
- def deny!
48
- result.load false
49
- throw :policy_fulfilled
50
- end
51
- end
52
-
53
- a = A.new
54
-
55
- (APolicy.new(record: a).apply(:show?) == BPolicy.new(record: a).apply(:show?)) || raise("Implementations are not equal")
56
-
57
- Benchmark.ips do |x|
58
- x.warmup = 0
59
-
60
- x.report("loop pre-check") do
61
- APolicy.new(record: a).apply(:show?)
62
- end
63
-
64
- x.report("catch/throw pre-check") do
65
- BPolicy.new(record: a).apply(:show?)
66
- end
67
-
68
- x.compare!
69
- end
70
-
71
- # Comparison:
72
- # catch/throw pre-check: 286094.2 i/s
73
- # loop pre-check: 184786.1 i/s - 1.55x slower
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "action_policy"
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 DELETED
@@ -1,8 +0,0 @@
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
File without changes
data/docs/CNAME DELETED
@@ -1 +0,0 @@
1
- actionpolicy.evilmartians.io
@@ -1,79 +0,0 @@
1
- [![Gem Version](https://badge.fury.io/rb/action_policy.svg)](https://badge.fury.io/rb/action_policy)
2
- [![Build Status](https://travis-ci.org/palkan/action_policy.svg?branch=master)](https://travis-ci.org/palkan/action_policy)
3
-
4
- ## Action Policy
5
-
6
- > Authorization framework for Ruby and Rails.
7
- <br>Composable. Extensible. Performant.
8
-
9
- **NOTE:** this documentation is for the version "0.3.0+".
10
-
11
- ## What is it?
12
-
13
- _Authorization_ is an act of giving **someone** official
14
- permission to **do something** (to not be confused with [_authentication_](https://en.wikipedia.org/wiki/Authentication)).
15
-
16
- Action Policy provides flexible tools to build an _authorization layer_ for your application.
17
-
18
- <div class="chart-container">
19
- <img src="assets/images/layer.svg" alt="Authorization layer" width="80%">
20
- </div>
21
-
22
- **NOTE:** Action Policy does not force you to use a specific authorization model (i.e., roles, permissions, etc.) and does not provide one. It only answers a single question: **How to verify access?**
23
-
24
- ## Where to go from here?
25
- - [Quick start](./quick_start.md)
26
- - [Using with Rails](./rails.md)
27
- - [Using with other Ruby frameworks](./non_rails.md)
28
- - [Using with GraphQL Ruby](./graphql.md)
29
-
30
- ## Project State
31
-
32
- The project is being used in production since mid 2018. Major features have been implemented, API has been stabilized. Check out our [development board](https://github.com/palkan/action_policy/projects/1) to see what's coming next.
33
-
34
- ## History
35
-
36
- Action Policy gem is an _extraction_-kind of a library. Most of the code has been used in production for several years in different [Evil Martians][] projects.
37
-
38
- We have decided to collect all our authorization techniques and pack them into a standalone gem–and that is how Action Policy was born!
39
-
40
- ## What about the existing solutions?
41
-
42
- Why did we decide to build our own authorization gem instead of using the existing solutions, such as [Pundit][] and [CanCanCan][]?
43
-
44
- **TL;DR they didn't solve all of our problems.**
45
-
46
- [Pundit][] has been our framework of choice for a long time. Being too _dead-simple_, it required a lot of hacking to fulfill business logic requirements.
47
-
48
- These _hacks_ later became Action Policy (initially, we even called it "Pundit, re-visited").
49
-
50
- We also took a few ideas from [CanCanCan][]—such as [default rules and rule aliases](./aliases.md).
51
-
52
- It is also worth noting that Action Policy (despite having a _Railsy_ name) is designed to be **Rails-free**. On the other hand, it contains some Rails-specific extensions and seamlessly integrates into the framework.
53
-
54
- So, what are the main reasons to consider Action Policy as your authorization tool?
55
-
56
- - **Performance**: multiple [caching strategies](./caching.md) out-of-the-box make authorization overhead as small as possible–especially useful when your rules involve DB queries; you can also monitor the performance and detect the bottlenecks using the built-in [instrumentation](./instrumentation) features.
57
-
58
- - **Composition & Customization**: use [only the features you need](./custom_policy.md) or easily extend the functionality–it's just Ruby classes and modules, (almost) zero magic! And you can add authorization [anywhere in your code](./non_rails.md), not only in controllers.
59
-
60
- - **Code Organization**: use [namespaces](./namespaces.md) to organize your policies (for example, when you have multiple authorization strategies); add [pre-checks](./pre_checks.md) to make rules more readable and better express your business-logic.
61
-
62
- - **...and more**: [testability](./testing.md), [i18n](./i18n.md) integrations, [actionable errors](./reasons.md).
63
-
64
- Learn more about the motivation behind the Action Policy and its features by watching this [RailsConf talk](https://www.youtube.com/watch?v=NVwx0DARDis).
65
-
66
- ## Resources
67
-
68
- - RubyRussia, 2019 "Welcome, or access denied?" talk ([video](https://www.youtube.com/watch?v=y15a2g7v8i0) [RU], [slides](https://speakerdeck.com/palkan/rubyrussia-2019-welcome-or-access-denied))
69
-
70
- - Seattle.rb, 2019 "A Denial!" talk [[slides](https://speakerdeck.com/palkan/seattle-dot-rb-2019-a-denial)]
71
-
72
- - RailsConf, 2018 "Access Denied" talk [[video](https://www.youtube.com/watch?v=NVwx0DARDis), [slides](https://speakerdeck.com/palkan/railsconf-2018-access-denied-the-missing-guide-to-authorization-in-rails)]
73
-
74
- <a href="https://evilmartians.com/?utm_source=action_policy">
75
- <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
76
-
77
- [CanCanCan]: https://github.com/CanCanCommunity/cancancan
78
- [Pundit]: https://github.com/varvet/pundit
79
- [Evil Martians]: https://evilmartians.com