Use Rolify for admin and moderator roles
This commit is contained in:
parent
a0c9641994
commit
946bb3ae9d
2
Gemfile
2
Gemfile
|
@ -42,6 +42,8 @@ gem 'tiny-color-rails'
|
|||
gem 'jquery-minicolors-rails'
|
||||
gem 'colorize'
|
||||
|
||||
gem "rolify", "~> 5.2"
|
||||
|
||||
source "https://rails-assets.org" do
|
||||
gem 'rails-assets-growl'
|
||||
gem 'rails-assets-jquery', '~> 2.2.0'
|
||||
|
|
|
@ -382,6 +382,7 @@ GEM
|
|||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rolify (5.2.0)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.1)
|
||||
|
@ -554,6 +555,7 @@ DEPENDENCIES
|
|||
rake
|
||||
redcarpet
|
||||
redis
|
||||
rolify (~> 5.2)
|
||||
rspec-rails (~> 3.9)
|
||||
ruby-progressbar
|
||||
sanitize
|
||||
|
|
52
Rakefile
52
Rakefile
|
@ -105,44 +105,48 @@ namespace :justask do
|
|||
end
|
||||
end
|
||||
|
||||
desc "Gives admin status to an user."
|
||||
desc "Gives admin status to a user."
|
||||
task :admin, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.admin = true
|
||||
user.save!
|
||||
puts "#{user.screen_name} is now an admin."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.add_role :administrator
|
||||
puts "#{user.screen_name} is now an administrator."
|
||||
end
|
||||
|
||||
desc "Removes admin status from an user."
|
||||
desc "Removes admin status from a user."
|
||||
task :deadmin, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.admin = false
|
||||
user.save!
|
||||
puts "#{user.screen_name} is no longer an admin."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.remove_role :administrator
|
||||
puts "#{user.screen_name} is no longer an administrator."
|
||||
end
|
||||
|
||||
desc "Gives moderator status to an user."
|
||||
desc "Gives moderator status to a user."
|
||||
task :mod, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.moderator = true
|
||||
user.save!
|
||||
puts "#{user.screen_name} is now an moderator."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.add_role :moderator
|
||||
puts "#{user.screen_name} is now a moderator."
|
||||
end
|
||||
|
||||
desc "Removes moderator status from an user."
|
||||
desc "Removes moderator status from a user."
|
||||
task :demod, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.moderator = false
|
||||
user.save!
|
||||
puts "#{user.screen_name} is no longer an moderator."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.remove_role :moderator
|
||||
puts "#{user.screen_name} is no longer a moderator."
|
||||
end
|
||||
|
||||
desc "Hits an user with the banhammer."
|
||||
|
|
|
@ -125,9 +125,9 @@ class Ajax::ModerationController < ApplicationController
|
|||
unban = params[:ban] == "0"
|
||||
perma = params[:permaban] == "1"
|
||||
|
||||
buntil = DateTime.strptime params[:until], "%m/%d/%Y %I:%M %p" unless unban or perma
|
||||
buntil = DateTime.strptime params[:until], "%m/%d/%Y %I:%M %p" unless unban || perma
|
||||
|
||||
if not unban and target.admin?
|
||||
if !unban && target.has_role?(:administrator)
|
||||
@status = :nopriv
|
||||
@message = I18n.t('messages.moderation.ban.nopriv')
|
||||
@success = false
|
||||
|
@ -166,7 +166,7 @@ class Ajax::ModerationController < ApplicationController
|
|||
@message = I18n.t('messages.moderation.privilege.nope')
|
||||
return unless %w(blogger supporter moderator admin contributor translator).include? params[:type].downcase
|
||||
|
||||
if %w(supporter moderator admin).include?(params[:type].downcase) and !current_user.admin?
|
||||
if %w(supporter moderator admin).include?(params[:type].downcase) && !current_user.has_role?(:administrator)
|
||||
@status = :nopriv
|
||||
@message = I18n.t('messages.moderation.privilege.nopriv')
|
||||
@success = false
|
||||
|
@ -174,7 +174,9 @@ class Ajax::ModerationController < ApplicationController
|
|||
end
|
||||
|
||||
@checked = status
|
||||
case params[:type].downcase
|
||||
type = params[:type].downcase
|
||||
target_role = {"admin" => "administrator"}.fetch(type, type).to_sym
|
||||
case type
|
||||
when 'blogger'
|
||||
target_user.blogger = status
|
||||
when 'contributor'
|
||||
|
@ -183,10 +185,12 @@ class Ajax::ModerationController < ApplicationController
|
|||
target_user.translator = status
|
||||
when 'supporter'
|
||||
target_user.supporter = status
|
||||
when 'moderator'
|
||||
target_user.moderator = status
|
||||
when 'admin'
|
||||
target_user.admin = status
|
||||
when 'moderator', 'admin'
|
||||
if status
|
||||
target_user.add_role target_role
|
||||
else
|
||||
target_user.remove_role target_role
|
||||
end
|
||||
end
|
||||
target_user.save!
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Role < ApplicationRecord
|
||||
has_and_belongs_to_many :users, join_table: :users_roles
|
||||
|
||||
belongs_to :resource,
|
||||
polymorphic: true,
|
||||
optional: true
|
||||
|
||||
validates :resource_type,
|
||||
inclusion: { in: Rolify.resource_types },
|
||||
allow_nil: true
|
||||
|
||||
scopify
|
||||
end
|
|
@ -5,6 +5,8 @@ class User < ApplicationRecord
|
|||
:recoverable, :rememberable, :trackable,
|
||||
:validatable, :confirmable, :authentication_keys => [:login]
|
||||
|
||||
rolify
|
||||
|
||||
# attr_accessor :login
|
||||
|
||||
has_many :questions, dependent: :destroy
|
||||
|
@ -183,7 +185,7 @@ class User < ApplicationRecord
|
|||
|
||||
# @return [Boolean] is the user a moderator?
|
||||
def mod?
|
||||
self.moderator? || self.admin?
|
||||
has_role?(:moderator) || has_role?(:administrator)
|
||||
end
|
||||
|
||||
# region stuff used for reporting/moderation
|
||||
|
@ -258,4 +260,10 @@ class User < ApplicationRecord
|
|||
end
|
||||
!self.export_processing
|
||||
end
|
||||
|
||||
# %w[admin moderator].each do |m|
|
||||
# define_method(m) { raise "not allowed: #{m}" }
|
||||
# define_method(m+??) { raise "not allowed: #{m}?"}
|
||||
# define_method(m+?=) { |*a| raise "not allowed: #{m}="}
|
||||
# end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
%i.fa.fa-fw.fa-cog
|
||||
= t('views.navigation.settings')
|
||||
%li.divider
|
||||
- if current_user.admin?
|
||||
- if current_user.has_role?(:administrator)
|
||||
%li
|
||||
%a{href: rails_admin_path}
|
||||
%i.fa.fa-fw.fa-cogs
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
%a{href: '#', data: { target: "#modal-privileges", toggle: :modal }}
|
||||
%i.fa.fa-wrench
|
||||
= raw t('views.actions.privilege', user: user.screen_name)
|
||||
- unless user.admin?
|
||||
- unless user.has_role?(:administrator)
|
||||
%li
|
||||
%a{href: '#', data: { target: "#modal-ban", toggle: :modal }}
|
||||
%i.fa.fa-ban
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
= render 'user/modal_privileges_item', privilege: 'blogger', description: t('views.modal.privilege.blogger'), user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'contributor', description: t('views.modal.privilege.contributor'), user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'translator', description: t('views.modal.privilege.translator'), user: @user
|
||||
- if current_user.admin?
|
||||
- if current_user.has_role?(:administrator)
|
||||
= render 'user/modal_privileges_item', privilege: 'supporter', description: t('views.modal.privilege.supporter'), user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'moderator', description: t('views.modal.privilege.moderator'),user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'admin', description: t('views.modal.privilege.admin'), user: @user
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
- description ||= ''
|
||||
- role_mapping = {"admin" => "administrator"}
|
||||
- requires_role = %w[admin moderator].include?(privilege)
|
||||
- checked = requires_role ? user.has_role?(role_mapping.fetch(privilege, privilege).to_sym) : user.public_send("#{privilege}?")
|
||||
%li.list-group-item{id: "privilege-#{privilege}"}
|
||||
.media
|
||||
.pull-left.j2-table
|
||||
%input.input--center{type: :checkbox, name: 'check-your-privileges', data: { type: privilege, user: user.screen_name }, checked: user.send("#{privilege}?"), autocomplete: 'off'}
|
||||
%input.input--center{type: :checkbox, name: 'check-your-privileges', data: { type: privilege, user: user.screen_name }, checked: checked, autocomplete: 'off'}
|
||||
.media-body
|
||||
.list-group-item-heading= privilege.capitalize
|
||||
- unless description.blank?
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.panel.panel-default#profile
|
||||
%img.profile--avatar{src: @user.profile_picture.url(:large)}
|
||||
- if user_signed_in? && current_user.admin?
|
||||
- if @user.admin?
|
||||
- if user_signed_in? && current_user.has_role?(:administrator)
|
||||
- if @user.has_role?(:administrator)
|
||||
.profile--panel-badge.panel-badge-danger
|
||||
%i.fa.fa-flask
|
||||
= t 'views.user.title.admin'
|
||||
- if @user.moderator?
|
||||
- if @user.has_role?(:moderator)
|
||||
.profile--panel-badge.panel-badge-success
|
||||
%i.fa.fa-users
|
||||
= t 'views.user.title.moderator'
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
|
||||
%p.data-heading Admin
|
||||
%p
|
||||
- if current_user.admin?
|
||||
- if current_user.has_role?(:administrator)
|
||||
%span.label.label-success
|
||||
%i.fa.fa-fw.fa-check
|
||||
- else
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# workaround to get pagination right
|
||||
if defined? WillPaginate
|
||||
Kaminari.configure do |config|
|
||||
|
@ -6,12 +8,11 @@ if defined? WillPaginate
|
|||
end
|
||||
|
||||
RailsAdmin.config do |config|
|
||||
|
||||
config.main_app_name = ['justask', 'Kontrollzentrum']
|
||||
|
||||
## == Authentication ==
|
||||
config.authenticate_with do
|
||||
redirect_to main_app.root_path unless current_user.try :admin?
|
||||
redirect_to main_app.root_path unless current_user&.has_role?(:administrator)
|
||||
end
|
||||
config.current_user_method(&:current_user)
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Rolify.configure do |config|
|
||||
# By default ORM adapter is ActiveRecord. uncomment to use mongoid
|
||||
# config.use_mongoid
|
||||
|
||||
# Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false
|
||||
# config.use_dynamic_shortcuts
|
||||
|
||||
# Configuration to remove roles from database once the last resource is removed. Default is: true
|
||||
config.remove_role_if_empty = false
|
||||
end
|
|
@ -2,19 +2,19 @@ require 'sidekiq/web'
|
|||
Rails.application.routes.draw do
|
||||
start = Time.now
|
||||
|
||||
# Admin panel
|
||||
mount RailsAdmin::Engine => '/justask_admin', as: 'rails_admin'
|
||||
|
||||
# Sidekiq
|
||||
constraints ->(req) { req.env["warden"].authenticate?(scope: :user) &&
|
||||
req.env['warden'].user.admin? } do
|
||||
req.env["warden"].user.has_role?(:administrator) } do
|
||||
# Admin panel
|
||||
mount RailsAdmin::Engine => "/justask_admin", as: "rails_admin"
|
||||
|
||||
mount Sidekiq::Web, at: "/sidekiq"
|
||||
mount PgHero::Engine, at: "/pghero", as: 'pghero'
|
||||
mount PgHero::Engine, at: "/pghero", as: "pghero"
|
||||
end
|
||||
|
||||
# Moderation panel
|
||||
constraints ->(req) { req.env['warden'].authenticate?(scope: :user) &&
|
||||
(req.env['warden'].user.mod?) } do
|
||||
constraints ->(req) { req.env["warden"].authenticate?(scope: :user) &&
|
||||
req.env["warden"].user.mod? } do
|
||||
match '/moderation/priority(/:user_id)', to: 'moderation#priority', via: :get, as: :moderation_priority
|
||||
match '/moderation/ip/:user_id', to: 'moderation#ip', via: :get, as: :moderation_ip
|
||||
match '/moderation(/:type)', to: 'moderation#index', via: :get, as: :moderation, defaults: {type: 'all'}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RolifyCreateRoles < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table(:roles) do |t|
|
||||
t.string :name
|
||||
t.references :resource, polymorphic: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table(:users_roles, id: false) do |t|
|
||||
t.references :user
|
||||
t.references :role
|
||||
end
|
||||
|
||||
add_index(:roles, %i[name resource_type resource_id])
|
||||
add_index(:users_roles, %i[user_id role_id])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateInitialRoles < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
%w[Administrator Moderator].each do |role|
|
||||
Role.where(name: role.parameterize).first_or_create
|
||||
end
|
||||
|
||||
{
|
||||
admin: :administrator,
|
||||
moderator: :moderator
|
||||
}.each do |legacy_role, new_role|
|
||||
User.where(legacy_role => true).each do |u|
|
||||
puts "-- migrating #{u.screen_name} (#{u.id}) from field:#{legacy_role} to role:#{new_role}"
|
||||
u.add_role new_role
|
||||
u.public_send("#{legacy_role}=", false)
|
||||
u.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
{
|
||||
administrator: :admin,
|
||||
moderator: :moderator
|
||||
}.each do |new_role, legacy_role|
|
||||
User.with_role(new_role).each do |u|
|
||||
puts "-- migrating #{u.screen_name} (#{u.id}) from role:#{new_role} to field:#{legacy_role}"
|
||||
u.public_send("#{legacy_role}=", true)
|
||||
u.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
db/schema.rb
20
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2016_01_05_165913) do
|
||||
ActiveRecord::Schema.define(version: 2020_04_19_185535) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -137,6 +137,16 @@ ActiveRecord::Schema.define(version: 2016_01_05_165913) do
|
|||
t.string "reason"
|
||||
end
|
||||
|
||||
create_table "roles", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "resource_type"
|
||||
t.bigint "resource_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
|
||||
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
|
||||
end
|
||||
|
||||
create_table "services", id: :serial, force: :cascade do |t|
|
||||
t.string "type", null: false
|
||||
t.integer "user_id", null: false
|
||||
|
@ -270,4 +280,12 @@ ActiveRecord::Schema.define(version: 2016_01_05_165913) do
|
|||
t.index ["screen_name"], name: "index_users_on_screen_name", unique: true
|
||||
end
|
||||
|
||||
create_table "users_roles", id: false, force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "role_id"
|
||||
t.index ["role_id"], name: "index_users_roles_on_role_id"
|
||||
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
|
||||
t.index ["user_id"], name: "index_users_roles_on_user_id"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
#
|
||||
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
||||
# Mayor.create(name: 'Emanuel', city: cities.first)
|
||||
|
||||
%w[Administrator Moderator].each do |role|
|
||||
Role.where(name: role.parameterize).first_or_create
|
||||
end
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
require 'httparty'
|
||||
require 'securerandom'
|
||||
|
||||
class Exporter
|
||||
EXPORT_ROLES = [:administrator, :moderator].freeze
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
@obj = {}
|
||||
|
@ -30,10 +34,10 @@ class Exporter
|
|||
private
|
||||
|
||||
def collect_user_info
|
||||
%i(admin answered_count asked_count ban_reason banned_until bio blogger comment_smiled_count commented_count
|
||||
%i(answered_count asked_count ban_reason banned_until bio blogger comment_smiled_count commented_count
|
||||
confirmation_sent_at confirmed_at contributor created_at crop_h crop_h_h crop_h_w crop_h_x crop_h_y
|
||||
crop_w crop_x crop_y current_sign_in_at current_sign_in_ip display_name email follower_count friend_count
|
||||
id last_sign_in_at last_sign_in_ip locale location moderator motivation_header permanently_banned
|
||||
id last_sign_in_at last_sign_in_ip locale location motivation_header permanently_banned
|
||||
privacy_allow_anonymous_questions privacy_allow_public_timeline privacy_allow_stranger_answers
|
||||
privacy_show_in_search profile_header_content_type profile_header_file_name profile_header_file_size
|
||||
profile_header_updated_at profile_picture_content_type profile_picture_file_name profile_picture_file_size
|
||||
|
@ -41,6 +45,10 @@ class Exporter
|
|||
updated_at website).each do |f|
|
||||
@obj[f] = @user.send f
|
||||
end
|
||||
|
||||
EXPORT_ROLES.each do |role|
|
||||
@obj[role] = @user.has_role?(role)
|
||||
end
|
||||
end
|
||||
|
||||
def collect_questions
|
||||
|
@ -221,11 +229,16 @@ class Exporter
|
|||
|
||||
def user_stub(user)
|
||||
uobj = {}
|
||||
%i(admin answered_count asked_count bio blogger comment_smiled_count commented_count contributor created_at
|
||||
display_name follower_count friend_count id location moderator motivation_header permanently_banned screen_name
|
||||
%i(answered_count asked_count bio blogger comment_smiled_count commented_count contributor created_at
|
||||
display_name follower_count friend_count id location motivation_header permanently_banned screen_name
|
||||
smiled_count supporter translator website).each do |f|
|
||||
uobj[f] = user.send f
|
||||
end
|
||||
|
||||
EXPORT_ROLES.each do |role|
|
||||
uobj[role] = user.has_role?(role)
|
||||
end
|
||||
|
||||
uobj
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'role-constrained routes', type: :request do
|
||||
shared_examples_for 'fails to access route' do
|
||||
it 'fails to access route' do
|
||||
# 404 = no user found -- we have a fallback route if something could not be matched
|
||||
expect(subject).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'routes for' do |roles, subject_block, skip_reason: nil|
|
||||
before { skip(skip_reason) } if skip_reason
|
||||
|
||||
subject(&subject_block)
|
||||
|
||||
context 'not signed in' do
|
||||
include_examples 'fails to access route'
|
||||
end
|
||||
|
||||
roles.each do |role|
|
||||
context "signed in user without #{role} role" do
|
||||
let(:user) { FactoryBot.create(:user, password: 'test1234') }
|
||||
|
||||
before(:each) do
|
||||
post '/sign_in', params: { user: { login: user.email, password: user.password } }
|
||||
end
|
||||
|
||||
include_examples 'fails to access route'
|
||||
end
|
||||
|
||||
context "signed in user with #{role} role" do
|
||||
let(:user) { FactoryBot.create(:user, password: 'test1234', roles: [role]) }
|
||||
|
||||
before(:each) do
|
||||
post '/sign_in', params: { user: { login: user.email, password: user.password } }
|
||||
end
|
||||
|
||||
it 'can access route' do
|
||||
expect(subject).to be_in 200..299
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like('routes for', [:administrator], -> { get('/justask_admin') })
|
||||
it_behaves_like('routes for', [:administrator], -> { get('/sidekiq') })
|
||||
it_behaves_like('routes for', [:administrator], -> { get('/pghero') }, skip_reason: 'PG::InFailedSqlTransaction due to 5.1 upgrade, works fine outside specs though')
|
||||
it_behaves_like('routes for', %i[administrator moderator], -> { get('/moderation') })
|
||||
end
|
Loading…
Reference in New Issue