Skip to content

Commit af57aed

Browse files
authored
Merge pull request #349 from TechAndCheck/299-add-user-roles
Add user roles
2 parents 06f7949 + d27e0a8 commit af57aed

27 files changed

+248
-65
lines changed

.rubocop.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ AllCops:
1919
# Join tables don't really need timestamps
2020
Rails/CreateTableWithTimestamps:
2121
Exclude:
22-
# - 'db/migrate/20180128231930_create_organizations_and_events.rb'
22+
- db/migrate/20220919194501_rolify_create_roles.rb
2323

2424
# Rails generates this file
2525
Style/BlockComments:
@@ -64,6 +64,10 @@ Rails/HasManyOrHasOneDependent:
6464

6565
Rails/HasAndBelongsToMany:
6666
Enabled: true
67+
Exclude:
68+
# Rolify uses HABTM. Despite a decade of the community attempting to implement
69+
# `has_many: through`, it still struggles mightily with it. Let's make an exception.
70+
- app/models/role.rb
6771

6872
Style/NumericPredicate:
6973
Enabled: true

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,6 @@ gem "country_select", "~> 8.0"
178178

179179
# Used for sending email through Mailgun
180180
gem "mailgun-ruby", "~> 1.2"
181+
182+
# Rolify is used for user roles
183+
gem "rolify", "~> 6.0"

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ GEM
326326
mime-types (>= 1.16, < 4.0)
327327
netrc (~> 0.8)
328328
rexml (3.2.5)
329+
rolify (6.0.0)
329330
rubocop (1.28.1)
330331
parallel (~> 1.10)
331332
parser (>= 3.1.0.0)
@@ -506,6 +507,7 @@ DEPENDENCIES
506507
rails (~> 7.0.2.3)
507508
rake
508509
redis (~> 4.0)
510+
rolify (~> 6.0)
509511
rubocop
510512
rubocop-minitest
511513
rubocop-performance

app/controllers/accounts_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def create
8787
raise InvalidTokenError if @user.new_record?
8888
raise InvalidUpdatePasswordError if typed_params.password.blank? || @user.invalid?
8989

90+
@user.remove_role :new_user
91+
9092
sign_in @user
9193
redirect_to after_sign_in_path_for(@user)
9294
end

app/controllers/application_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ def authenticate_super_user
7474
# First we make sure they're logged in at all, this also sets the current user so we can check it
7575
return false unless authenticate_user!
7676

77-
current_user.super_admin?
77+
current_user.is_admin?
7878
end
7979

8080
sig { void }
8181
def authenticate_super_user!
8282
# First we make sure they're logged in at all, this also sets the current user so we can check it
8383
authenticate_user!
8484

85-
unless current_user.super_admin?
85+
unless current_user.is_admin?
8686
redirect_back_or_to "/", allow_other_host: false, alert: "You must be a super user/admin to access this page."
8787
end
8888
end

app/models/role.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Role < ApplicationRecord
2+
has_and_belongs_to_many :users, join_table: :users_roles
3+
4+
belongs_to :resource,
5+
polymorphic: true,
6+
optional: true
7+
8+
validates :resource_type,
9+
inclusion: { in: Rolify.resource_types },
10+
allow_nil: true
11+
12+
scopify
13+
end

app/models/user.rb

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# typed: strict
22

33
class User < ApplicationRecord
4+
rolify
5+
devise :database_authenticatable, :registerable,
6+
:recoverable, :rememberable, :validatable,
7+
:trackable, :lockable, :confirmable
8+
49
has_many :api_keys, dependent: :delete_all
510
has_many :archive_items, foreign_key: :submitter_id, dependent: :nullify
611

@@ -9,17 +14,6 @@ class User < ApplicationRecord
914

1015
has_one :applicant, dependent: :destroy
1116

12-
# Include default devise modules. Others available are:
13-
# :timeoutable and :omniauthable
14-
devise :database_authenticatable, :registerable,
15-
:recoverable, :rememberable, :validatable,
16-
:trackable, :lockable, :confirmable
17-
18-
sig { returns(T::Boolean) }
19-
def super_admin?
20-
self.super_admin
21-
end
22-
2317
# `Devise::Recoverable#set_reset_password_token` is a protected method, which prevents us from
2418
# calling it directly. Since we need to be able to do that for tests and for duck-punching other
2519
# `Devise::Recoverable` methods, we pull it into the public space here.
@@ -33,7 +27,7 @@ def set_reset_password_token
3327
# Like the original method, it also creates the user's `reset_password_token`.
3428
sig { returns(String) }
3529
def send_setup_instructions
36-
raise AlreadySetupError if sign_in_count.positive?
30+
raise AlreadySetupError unless self.is_new_user?
3731

3832
token = set_reset_password_token
3933

@@ -54,7 +48,7 @@ def send_setup_instructions
5448
def self.create_from_applicant(applicant)
5549
raise ApplicantNotApprovedError unless applicant.approved?
5650

57-
self.create!({
51+
user = self.create!({
5852
applicant: applicant,
5953
email: applicant.email,
6054
# The user will have to change their password immediately. This is just to pass validation.
@@ -64,6 +58,20 @@ def self.create_from_applicant(applicant)
6458
confirmed_at: applicant.confirmed_at,
6559
confirmation_sent_at: applicant.confirmation_sent_at
6660
})
61+
62+
user.assign_default_roles
63+
64+
user
65+
end
66+
67+
# All new users are implicitly Insights users.
68+
# All new users are also "new" until they have completed their initial setup.
69+
sig { void }
70+
def assign_default_roles
71+
if self.roles.blank?
72+
self.add_role :new_user
73+
self.add_role :insights_user
74+
end
6775
end
6876
end
6977

app/views/layouts/_header.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<li>
1919
<%= link_to "Settings", account_path, class: "block no-underline py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white" %>
2020
</li>
21-
<% if current_user.super_admin? %>
21+
<% if current_user.is_admin? %>
2222
<li>
2323
<%= link_to jobs_status_index_path, class: "flex flex-inline gap-1 block no-underline py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white" do %>
2424
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd" /></svg>

config/initializers/rolify.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Rolify.configure do |config|
2+
# By default ORM adapter is ActiveRecord. uncomment to use mongoid
3+
# config.use_mongoid
4+
5+
# Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false
6+
#
7+
# Enabled because these are convenient methods, and according to the Rolify documentation they
8+
# are generated at boot time (and when `add_role` is run), so shouldn't hurt performance.
9+
config.use_dynamic_shortcuts
10+
11+
# Configuration to remove roles from database once the last resource is removed. Default is: true
12+
#
13+
# Toggled to false because we have well-defined user roles that we don't want removed, even if
14+
# the last user using them is deleted.
15+
config.remove_role_if_empty = false
16+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class RolifyCreateRoles < ActiveRecord::Migration[7.0]
2+
def change
3+
create_table "roles", id: :uuid do |t|
4+
t.string :name
5+
t.references :resource, polymorphic: true
6+
7+
t.timestamps
8+
end
9+
10+
create_table "users_roles", id: false do |t|
11+
t.references :user, type: :uuid
12+
t.references :role, type: :uuid
13+
end
14+
15+
add_index :roles, [ :name, :resource_type, :resource_id ]
16+
add_index :users_roles, [ :user_id, :role_id ]
17+
end
18+
end

0 commit comments

Comments
 (0)