api_guardian 0.1.0.pre

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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +125 -0
  4. data/Rakefile +30 -0
  5. data/app/controllers/api_guardian/api_controller.rb +112 -0
  6. data/app/controllers/api_guardian/application_controller.rb +11 -0
  7. data/app/controllers/api_guardian/permissions_controller.rb +7 -0
  8. data/app/controllers/api_guardian/registration_controller.rb +38 -0
  9. data/app/controllers/api_guardian/roles_controller.rb +19 -0
  10. data/app/controllers/api_guardian/users_controller.rb +20 -0
  11. data/app/models/api_guardian/permission.rb +14 -0
  12. data/app/models/api_guardian/role.rb +97 -0
  13. data/app/models/api_guardian/role_permission.rb +8 -0
  14. data/app/models/api_guardian/user.rb +23 -0
  15. data/app/serializers/api_guardian/permission_serializer.rb +7 -0
  16. data/app/serializers/api_guardian/role_serializer.rb +7 -0
  17. data/app/serializers/api_guardian/user_serializer.rb +10 -0
  18. data/config/initializers/api_guardian.rb +10 -0
  19. data/config/initializers/doorkeeper.rb +143 -0
  20. data/config/routes.rb +20 -0
  21. data/db/migrate/20151117191338_api_guardian_enable_uuid_extension.rb +5 -0
  22. data/db/migrate/20151117191911_create_api_guardian_roles.rb +9 -0
  23. data/db/migrate/20151117195618_create_api_guardian_users.rb +25 -0
  24. data/db/migrate/20151117212826_create_api_guardian_permissions.rb +10 -0
  25. data/db/migrate/20151117213145_create_api_guardian_role_permissions.rb +11 -0
  26. data/db/migrate/20151117225238_create_doorkeeper_tables.rb +42 -0
  27. data/db/seeds.rb +32 -0
  28. data/lib/api_guardian.rb +80 -0
  29. data/lib/api_guardian/concerns/api_errors/handler.rb +145 -0
  30. data/lib/api_guardian/concerns/api_errors/renderer.rb +45 -0
  31. data/lib/api_guardian/concerns/api_request/validator.rb +66 -0
  32. data/lib/api_guardian/configuration.rb +171 -0
  33. data/lib/api_guardian/engine.rb +23 -0
  34. data/lib/api_guardian/errors/invalid_content_type_error.rb +6 -0
  35. data/lib/api_guardian/errors/invalid_permission_name_error.rb +6 -0
  36. data/lib/api_guardian/errors/invalid_request_body_error.rb +6 -0
  37. data/lib/api_guardian/errors/invalid_request_resource_id_error.rb +6 -0
  38. data/lib/api_guardian/errors/invalid_request_resource_type_error.rb +6 -0
  39. data/lib/api_guardian/errors/invalid_update_action_error.rb +6 -0
  40. data/lib/api_guardian/errors/reset_token_expired_error.rb +6 -0
  41. data/lib/api_guardian/errors/reset_token_user_mismatch_error.rb +6 -0
  42. data/lib/api_guardian/policies/application_policy.rb +65 -0
  43. data/lib/api_guardian/policies/permission_policy.rb +15 -0
  44. data/lib/api_guardian/policies/role_policy.rb +15 -0
  45. data/lib/api_guardian/policies/user_policy.rb +23 -0
  46. data/lib/api_guardian/stores/base.rb +53 -0
  47. data/lib/api_guardian/stores/permission_store.rb +6 -0
  48. data/lib/api_guardian/stores/role_store.rb +9 -0
  49. data/lib/api_guardian/stores/user_store.rb +86 -0
  50. data/lib/api_guardian/version.rb +3 -0
  51. data/lib/generators/api_guardian/install/USAGE +8 -0
  52. data/lib/generators/api_guardian/install/install_generator.rb +19 -0
  53. data/lib/generators/api_guardian/install/templates/README +1 -0
  54. data/lib/generators/api_guardian/install/templates/api_guardian.rb +5 -0
  55. data/lib/tasks/api_guardian_tasks.rake +4 -0
  56. data/spec/concerns/api_errors/handler_spec.rb +114 -0
  57. data/spec/concerns/api_request/validator_spec.rb +102 -0
  58. data/spec/dummy/README.rdoc +28 -0
  59. data/spec/dummy/Rakefile +6 -0
  60. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  61. data/spec/dummy/bin/bundle +3 -0
  62. data/spec/dummy/bin/rails +4 -0
  63. data/spec/dummy/bin/rake +4 -0
  64. data/spec/dummy/bin/setup +29 -0
  65. data/spec/dummy/config.ru +4 -0
  66. data/spec/dummy/config/application.rb +25 -0
  67. data/spec/dummy/config/boot.rb +5 -0
  68. data/spec/dummy/config/database.yml +13 -0
  69. data/spec/dummy/config/environment.rb +5 -0
  70. data/spec/dummy/config/environments/development.rb +41 -0
  71. data/spec/dummy/config/environments/production.rb +79 -0
  72. data/spec/dummy/config/environments/test.rb +42 -0
  73. data/spec/dummy/config/initializers/assets.rb +11 -0
  74. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  75. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  76. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  77. data/spec/dummy/config/initializers/inflections.rb +16 -0
  78. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  79. data/spec/dummy/config/initializers/session_store.rb +3 -0
  80. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  81. data/spec/dummy/config/locales/en.yml +23 -0
  82. data/spec/dummy/config/routes.rb +3 -0
  83. data/spec/dummy/config/secrets.yml +22 -0
  84. data/spec/dummy/db/schema.rb +104 -0
  85. data/spec/dummy/log/test.log +5031 -0
  86. data/spec/dummy/public/404.html +67 -0
  87. data/spec/dummy/public/422.html +67 -0
  88. data/spec/dummy/public/500.html +66 -0
  89. data/spec/dummy/public/favicon.ico +0 -0
  90. data/spec/factories/permissions.rb +6 -0
  91. data/spec/factories/role_permissions.rb +6 -0
  92. data/spec/factories/roles.rb +24 -0
  93. data/spec/factories/users.rb +11 -0
  94. data/spec/models/permission_spec.rb +28 -0
  95. data/spec/models/role_permission_spec.rb +27 -0
  96. data/spec/models/role_spec.rb +209 -0
  97. data/spec/models/user_spec.rb +44 -0
  98. data/spec/policies/application_policy_spec.rb +118 -0
  99. data/spec/policies/permission_policy_spec.rb +28 -0
  100. data/spec/policies/role_policy_spec.rb +28 -0
  101. data/spec/policies/user_policy_spec.rb +29 -0
  102. data/spec/requests/permissions_controller_spec.rb +19 -0
  103. data/spec/requests/registration_controller_spec.rb +151 -0
  104. data/spec/requests/roles_controller_spec.rb +75 -0
  105. data/spec/requests/users_controller_spec.rb +75 -0
  106. data/spec/spec_helper.rb +138 -0
  107. data/spec/stores/base_spec.rb +113 -0
  108. data/spec/stores/permission_store_spec.rb +2 -0
  109. data/spec/stores/role_store_spec.rb +12 -0
  110. data/spec/stores/user_store_spec.rb +144 -0
  111. data/spec/support/controller_concern_test_helpers.rb +21 -0
  112. data/spec/support/matchers.rb +37 -0
  113. data/spec/support/request_helpers.rb +111 -0
  114. metadata +508 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b3cb31c1deac4f774db0a75388fdc08c6b490f95
4
+ data.tar.gz: 44fc98728a248791eeee42aaeb18191db8652afb
5
+ SHA512:
6
+ metadata.gz: d5a0bb945c2c13444138979d60703de6c47ef2239542a73cd3c607652faef4a8ba65613b5e0a4a79464dcb85b4bda6fe44b0a96e251bc9ce2c97175a5686426b
7
+ data.tar.gz: 510271fa761b9fc922fce424fe94da0c47e7594b8aa24aa8aec8bdf8b8375395aa6177a2130cbbc0bea3941844e6a71c1499a8ff05e5e719fb20d3268a0d3a47
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Travis Vignon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Api Guardian
2
+
3
+ Drop in authorization and authentication suite for Rails APIs.
4
+
5
+ ## **\*\*This gem is in alpha stages and is not feature complete. It should not be used in production!\*\***
6
+
7
+ ## Overview
8
+
9
+ ApiGuardian includes the following features out of the box:
10
+
11
+ * User registration (email/pass)
12
+ * Password reset workflow
13
+ * Roles
14
+ * Permissions
15
+ * Stateless authentication using OAuth2 (via [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) and [Doorkeeper::JWT](https://github.com/chriswarren/doorkeeper-jwt))
16
+ * Policy enforcement (via [Pundit](https://github.com/elabs/pundit))
17
+ * Serialization to [JSON API](http://jsonapi.org/) (via [AMS](https://github.com/rails-api/active_model_serializers))
18
+ * Two-factor auth (TODO)
19
+ * External Login (TODO)
20
+
21
+ What doesn't it include?
22
+
23
+ * Stateful session support (Cookies)
24
+ * HTML/CSS/JS or views of any kind.
25
+
26
+ ## Requirements
27
+
28
+ * PostgreSQL >= 9.1 (uuid-ossp support)
29
+
30
+ **Note: For now, your app must use a PostgreSQL database.** This is because ApiGuardian is using UUID primary keys for all records.
31
+
32
+ ## Installation
33
+
34
+ ### First
35
+
36
+ Put this in your Gemfile:
37
+
38
+ ```rb
39
+ gem 'api_guardian'
40
+ ```
41
+
42
+ ### Second
43
+
44
+ Run this command:
45
+
46
+ ```sh
47
+ rake generate api_guardian:install
48
+ ```
49
+
50
+ This will add an initializer, mount the routes, and, copy the migrations/seed files.
51
+ You will need to follow this with:
52
+
53
+ ```sh
54
+ rake db:migrate
55
+ ```
56
+
57
+ ### Third
58
+
59
+ To Do
60
+
61
+ ### Finally
62
+
63
+ To Do
64
+
65
+ ## Usage
66
+
67
+ ### Roles
68
+
69
+ To Do
70
+
71
+ ### Permissions
72
+
73
+ To Do
74
+
75
+ ### Users
76
+
77
+ To Do
78
+
79
+ ## Roadmap
80
+
81
+ * controller actions:
82
+ * Assign permissions to role by name
83
+ * validate user password
84
+ * config
85
+ * password settings (44:1?)
86
+ * devise_zxcvbn
87
+ * user lockouts
88
+ * 2fa settings
89
+ * ???
90
+ * Generators
91
+ * install (initializer, migrations, seed, routes)
92
+ * ???
93
+ * omniauth
94
+ * Request logging
95
+ * Sessions/Devices (attach to tokens)
96
+ * Activity/Events (User signed in, User authenticated at...)
97
+ * Email Service/SMS Service
98
+ * Account lockout
99
+ * SSO
100
+ * digits integration
101
+ * Multi-tenancy
102
+ * Account lockout (failed login attempts)
103
+ * 2FA
104
+ * http://blog.meldium.com/home/2013/8/23/screw-up-two-factor-authentication
105
+ * https://www.authy.com/product/
106
+ * https://github.com/heapsource/active_model_otp + Twilio
107
+ * Fix for JWT storage: https://github.com/doorkeeper-gem/doorkeeper/wiki/How-to-fix-PostgreSQL-error-on-index-row-size
108
+ * Cache
109
+
110
+ ## Getting Help
111
+
112
+ If you find a bug, please report an [Issue](https://github.com/lookitsatravis/api_guardian/issues).
113
+
114
+ If you have a question, please post to [Stack Overflow](https://stackoverflow.com/questions/tagged/api_guardian).
115
+
116
+ Thanks!
117
+
118
+ ## Contributing
119
+
120
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
121
+
122
+ ## License
123
+
124
+ ApiGuardian is copyright © 2015 Travis Vignon. It is free software, and may be
125
+ redistributed under the terms specified in the [`MIT-LICENSE`](MIT-LICENSE) file.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Guarantor'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+
27
+ desc 'Run all specs in spec directory (excluding plugin specs)'
28
+ RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
29
+
30
+ task default: :spec
@@ -0,0 +1,112 @@
1
+ # TODO: User. should be moved to UserStore.
2
+ module ApiGuardian
3
+ class ApiController < ActionController::API
4
+ include ::Pundit
5
+ include ApiGuardian::Concerns::ApiErrors::Handler
6
+ include ApiGuardian::Concerns::ApiRequest::Validator
7
+
8
+ before_action :doorkeeper_authorize!, except: [:not_found]
9
+ before_action :set_current_user
10
+ before_action :prep_response
11
+ before_action :validate_api_request, except: [:not_found]
12
+ before_action :find_and_authorize_resource, except: [:index, :new, :create, :not_found]
13
+ after_action :verify_policy_scoped, only: :index
14
+ after_action :verify_authorized, except: [:index, :not_found]
15
+
16
+ rescue_from Exception, with: :api_error_handler
17
+
18
+ attr_reader :current_user
19
+
20
+ def index
21
+ @resources = resource_store.paginate(page_params[:number], page_params[:size])
22
+ render json: @resources, include: includes
23
+ end
24
+
25
+ def show
26
+ render json: @resource, include: includes
27
+ end
28
+
29
+ def create
30
+ authorize resource_class
31
+ @resource = resource_store.create(create_resource_params)
32
+ render json: @resource, status: :created, include: includes
33
+ end
34
+
35
+ def update
36
+ @resource = resource_store.update(@resource, update_resource_params)
37
+ render json: @resource, include: includes
38
+ end
39
+
40
+ def destroy
41
+ @resource.destroy!
42
+ head :no_content
43
+ end
44
+
45
+ def not_found
46
+ render_not_found
47
+ end
48
+
49
+ protected
50
+
51
+ def find_and_authorize_resource
52
+ @resource = resource_store.find(params[:id])
53
+ authorize @resource
54
+ end
55
+
56
+ def resource_store
57
+ @resource_store ||= ('ApiGuardian::Stores::' + resource_name + 'Store').constantize.new(policy_scope(resource_class))
58
+ end
59
+
60
+ def resource_name
61
+ @resource_name ||= controller_name.classify
62
+ end
63
+
64
+ def resource_class
65
+ @resource_class ||= ApiGuardian.send("#{resource_name.downcase}_class")
66
+ end
67
+
68
+ # :nocov:
69
+ def includes
70
+ fail 'This needs to be overriden by a child of the API ApplicationController.'
71
+ end
72
+ # :nocov:
73
+
74
+ def create_resource_params
75
+ params.require(:data).require(:attributes).permit(create_params)
76
+ end
77
+
78
+ def update_resource_params
79
+ params.require(:data).require(:attributes).permit(update_params)
80
+ end
81
+
82
+ # :nocov:
83
+ def create_params
84
+ fail 'This needs to be overriden by a child of the API ApplicationController.'
85
+ end
86
+ # :nocov:
87
+
88
+ def page_params
89
+ params.fetch(:page, number: 1, size: 25)
90
+ end
91
+
92
+ # :nocov:
93
+ def update_params
94
+ fail 'This needs to be overriden by a child of the API ApplicationController.'
95
+ end
96
+ # :nocov:
97
+
98
+ private
99
+
100
+ def set_current_user
101
+ @current_user = ApiGuardian.user_class.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
102
+ end
103
+
104
+ def current_resource_owner
105
+ ApiGuardian.user_class.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
106
+ end
107
+
108
+ def prep_response
109
+ response.headers['Content-Type'] = 'application/vnd.api+json'
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,11 @@
1
+ module ApiGuardian
2
+ class ApplicationController < ActionController::API
3
+ include ApiGuardian::Concerns::ApiErrors::Handler
4
+
5
+ rescue_from Exception, with: :api_error_handler
6
+
7
+ def not_found
8
+ render_not_found
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module ApiGuardian
2
+ class PermissionsController < ApiController
3
+ def includes
4
+ []
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module ApiGuardian
2
+ class RegistrationController < ApiGuardian::ApplicationController
3
+ def create
4
+ @user = ApiGuardian::Stores::UserStore.register(register_params)
5
+ render json: @user, status: :created, include: ['role']
6
+ end
7
+
8
+ def reset_password
9
+ if ApiGuardian::Stores::UserStore.reset_password(reset_password_params)
10
+ head :no_content
11
+ else
12
+ render_not_found
13
+ end
14
+ end
15
+
16
+ def complete_reset_password
17
+ if ApiGuardian::Stores::UserStore.complete_reset_password(complete_reset_password_params)
18
+ head :no_content
19
+ else
20
+ render_not_found
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def register_params
27
+ params.permit(:email, :password, :password_confirmation)
28
+ end
29
+
30
+ def reset_password_params
31
+ params.fetch(:email)
32
+ end
33
+
34
+ def complete_reset_password_params
35
+ params.permit(:token, :email, :password, :password_confirmation)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ module ApiGuardian
2
+ class RolesController < ApiController
3
+ protected
4
+
5
+ def includes
6
+ []
7
+ end
8
+
9
+ def create_params
10
+ [
11
+ :name, :default
12
+ ]
13
+ end
14
+
15
+ def update_params
16
+ create_params
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module ApiGuardian
2
+ class UsersController < ApiController
3
+ protected
4
+
5
+ def includes
6
+ ['role']
7
+ end
8
+
9
+ def create_params
10
+ [
11
+ :first_name, :last_name, :email, :phone_number, :role_id, :password,
12
+ :password_confirmation
13
+ ]
14
+ end
15
+
16
+ def update_params
17
+ create_params
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module ApiGuardian
2
+ class Permission < ActiveRecord::Base
3
+ has_many :role_permissions, class_name: ApiGuardian.role_permission_class.to_s
4
+ has_many :roles, through: :role_permissions, class_name: ApiGuardian.role_class.to_s
5
+
6
+ validates :name, uniqueness: true
7
+ validates :name, :desc, presence: true
8
+
9
+ # Class Methods
10
+ def self.policy_class
11
+ ApiGuardian::Policies::PermissionPolicy
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,97 @@
1
+ # TODO: Permission. should be moved to PermissionStore.
2
+ module ApiGuardian
3
+ class Role < ActiveRecord::Base
4
+ has_many :users, class_name: ApiGuardian.user_class.to_s
5
+ has_many :role_permissions, class_name: ApiGuardian.role_permission_class.to_s
6
+
7
+ validates :name, uniqueness: true, presence: true
8
+ validates :default, uniqueness: true, if: proc { |r| r.default? }
9
+
10
+ scope :default, -> { where(default: true) }
11
+
12
+ # Class Methods
13
+ def self.default_role
14
+ default.first
15
+ end
16
+
17
+ def self.policy_class
18
+ ApiGuardian::Policies::RolePolicy
19
+ end
20
+
21
+ # Instance Methods
22
+ def can?(action)
23
+ if action.is_a?(Array)
24
+ grants = []
25
+ action.each do |a|
26
+ perm = Permission.find_by_name(action)
27
+ fail ApiGuardian::Errors::InvalidPermissionNameError, "Permission '#{a}' is not valid." unless perm
28
+
29
+ role_permissions.includes(:permission).find_each do |rp|
30
+ grants.push rp.granted if rp.permission.name == a
31
+ end
32
+ end
33
+ return grants.include? true if grants.count > 0 # otherwise this permission wasn't found at all
34
+ else
35
+ perm = Permission.find_by_name(action)
36
+ fail ApiGuardian::Errors::InvalidPermissionNameError, "Permission '#{action}' is not valid." unless perm
37
+
38
+ role_permissions.includes(:permission).find_each do |rp|
39
+ return rp.granted if rp.permission.name == action
40
+ end
41
+ end
42
+
43
+ false
44
+ end
45
+
46
+ def cannot?(action)
47
+ !can? action
48
+ end
49
+
50
+ def permissions
51
+ arr = role_permissions.includes(:permission).map do |rp|
52
+ rp.permission.name if rp.granted
53
+ end.compact
54
+
55
+ # We want to simplify returned permissions when user has "manage" for a
56
+ # given resource by only returing that one instead of individual ones.
57
+ arr.map do |p|
58
+ val = nil
59
+ if p.include? ':manage'
60
+ val = p
61
+ else
62
+ resource = p.split(':')[0]
63
+ val = p unless arr.include? "#{resource}:manage"
64
+ end
65
+ val
66
+ end.compact
67
+ end
68
+
69
+ def create_default_permissions(granted)
70
+ Permission.find_each do |p|
71
+ role_permissions.create(permission: p, granted: granted) unless role_permissions.include? p
72
+ end
73
+ end
74
+
75
+ def add_permission(name)
76
+ perm = Permission.find_by_name(name)
77
+ fail ApiGuardian::Errors::InvalidPermissionNameError, "Permission '#{name}' is not valid." unless perm
78
+
79
+ role_permissions.each do |rp|
80
+ return rp.update_attribute(:granted, true) if rp.permission.name == name
81
+ end
82
+
83
+ role_permissions.create(permission: perm, granted: true)
84
+ end
85
+
86
+ def remove_permission(name, destroy = false)
87
+ role_permissions.each do |rp|
88
+ next unless rp.permission.name == name
89
+ if destroy
90
+ rp.destroy
91
+ else
92
+ rp.update_attribute(:granted, false)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end