api_guardian 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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