Implement roles and visibility (#128)

* Add migrations to add user roles and party visibility.

* Update schema.rb

* Add admin check in User model

* Implement rudimentary visibility of teams

* Adds checks to Party model
* Hides parties from collection views depending on visibility
* Disallows viewing private parties if you're not the owner

* Add a party's visibility to blueprint

* Add admin mode

The API Controller checks if the user is logged in and whether they are an admin, and checks for the X-Admin-Mode header

* Implement admin mode overrides

* Add admin_mode to authorize

* Note to self: Implement user editing by admins

* Fix syntax error with equality in SQL

* Fix syntax error with method name

* Fix bug in who can see restricted parties

* Add privacy control to user profiles
This commit is contained in:
Justin Edmund 2023-08-25 15:53:56 -07:00 committed by GitHub
parent c79d2717cc
commit 8381c668bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 600 additions and 525 deletions

View file

@ -35,7 +35,7 @@ module Api
view :minimal do
fields :name, :element, :shortcode, :favorited, :remix,
:extra, :full_auto, :clear_time, :auto_guard, :auto_summon,
:created_at, :updated_at
:visibility, :created_at, :updated_at
field :guidebooks do |p|
{

View file

@ -50,6 +50,14 @@ module Api
@current_user
end
def admin_mode
if current_user && current_user.admin? && request.headers['X-Admin-Mode']
@admin_mode ||= request.headers['X-Admin-Mode'] == 'true'
end
@admin_mode
end
def edit_key
@edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key']

View file

@ -32,6 +32,11 @@ module Api
end
def show
# If a party is private, check that the user is the owner or an admin
if (@party.private? && !current_user) || (@party.private? && not_owner && !admin_mode)
return render_unauthorized_response
end
return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party
render_not_found_response('project')
@ -90,7 +95,7 @@ module Api
conditions = build_filters
conditions[:favorites] = { user_id: current_user.id }
query = build_query(conditions, true)
query = build_query(conditions, favorites: true)
query = apply_includes(query, params[:includes]) if params[:includes].present?
query = apply_excludes(query, params[:excludes]) if params[:excludes].present?
@ -104,7 +109,11 @@ module Api
private
def authorize
render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key
render_unauthorized_response if (not_owner && !admin_mode) || (@party.edit_key != edit_key && !admin_mode)
end
def not_owner
current_user && @party.user != current_user
end
def build_filters
@ -152,11 +161,12 @@ module Api
value.to_i unless value.blank? || value.to_i == -1
end
def build_query(conditions, favorites = false)
def build_query(conditions, favorites: false)
query = Party.distinct
.joins(weapons: [:object], summons: [:object], characters: [:object])
.group('parties.id')
.where(conditions)
.where(privacy(favorites: favorites))
.where(name_quality)
.where(user_quality)
.where(original)
@ -242,6 +252,16 @@ module Api
})
end
def privacy(favorites: false)
return if admin_mode
if favorites
'visibility < 3'
else
'visibility = 1'
end
end
def user_quality
'user_id IS NOT NULL' unless request.params[:user_quality].blank? || request.params[:user_quality] == 'false'
end
@ -329,6 +349,7 @@ module Api
:description,
:raid_id,
:job_id,
:visibility,
:accessory_id,
:skill0_id,
:skill1_id,

View file

@ -41,6 +41,8 @@ module Api
render_validation_error_response(@user)
end
# TODO: Allow admins to update other users
def update
render json: UserBlueprint.render(@user, view: :minimal) if @user.update(user_params)
end
@ -61,6 +63,7 @@ module Api
.where(name_quality)
.where(user_quality)
.where(original)
.where(privacy)
.order(created_at: :desc)
.paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE)
.each do |party|
@ -113,9 +116,18 @@ module Api
hash[:created_at] = start_time..DateTime.current unless params['recency'].blank?
# Advanced filters: Team parameters
hash[:full_auto] = params['full_auto'].to_i unless params['full_auto'].blank? || params['full_auto'].to_i == -1
hash[:auto_guard] = params['auto_guard'].to_i unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1
hash[:charge_attack] = params['charge_attack'].to_i unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1
unless params['full_auto'].blank? || params['full_auto'].to_i == -1
hash[:full_auto] =
params['full_auto'].to_i
end
unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1
hash[:auto_guard] =
params['auto_guard'].to_i
end
unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1
hash[:charge_attack] =
params['charge_attack'].to_i
end
# Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1
# How do we do the same for button count since that can reasonably be 1?
@ -131,38 +143,44 @@ module Api
end
def original
unless params.key?('original') || params['original'].blank? || params['original'] == '0'
"source_party_id IS NULL"
end
return if params.key?('original') || params['original'].blank? || params['original'] == '0'
'source_party_id IS NULL'
end
def user_quality
unless params.key?('user_quality') || params[:user_quality].nil? || params[:user_quality] == "0"
"user_id IS NOT NULL"
end
return if params.key?('user_quality') || params[:user_quality].nil? || params[:user_quality] == '0'
'user_id IS NOT NULL'
end
def name_quality
low_quality = [
"Untitled",
"Remix of Untitled",
"Remix of Remix of Untitled",
"Remix of Remix of Remix of Untitled",
"Remix of Remix of Remix of Remix of Untitled",
"Remix of Remix of Remix of Remix of Remix of Untitled",
"無題",
"無題のリミックス",
"無題のリミックスのリミックス",
"無題のリミックスのリミックスのリミックス",
"無題のリミックスのリミックスのリミックスのリミックス",
"無題のリミックスのリミックスのリミックスのリミックスのリミックス"
'Untitled',
'Remix of Untitled',
'Remix of Remix of Untitled',
'Remix of Remix of Remix of Untitled',
'Remix of Remix of Remix of Remix of Untitled',
'Remix of Remix of Remix of Remix of Remix of Untitled',
'無題',
'無題のリミックス',
'無題のリミックスのリミックス',
'無題のリミックスのリミックスのリミックス',
'無題のリミックスのリミックスのリミックスのリミックス',
'無題のリミックスのリミックスのリミックスのリミックスのリミックス'
]
joined_names = low_quality.map { |name| "'#{name}'" }.join(',')
unless params.key?('name_quality') || params[:name_quality].nil? || params[:name_quality] == "0"
return if params.key?('name_quality') || params[:name_quality].nil? || params[:name_quality] == '0'
"name NOT IN (#{joined_names})"
end
def privacy
return if admin_mode
'visibility = 1' if current_user != @user
end
# Specify whitelisted properties that can be modified.

View file

@ -105,17 +105,29 @@ class Party < ApplicationRecord
end
def is_remix
self.source_party != nil
!source_party.nil?
end
def remixes
Party.where(source_party_id: self.id)
Party.where(source_party_id: id)
end
def blueprint
PartyBlueprint
end
def public?
visibility == 1
end
def unlisted?
visibility == 2
end
def private?
visibility == 3
end
private
def set_shortcode
@ -123,10 +135,10 @@ class Party < ApplicationRecord
end
def set_edit_key
if !self.user
return if user
self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join)
end
end
def random_string
num_chars = 6

View file

@ -43,6 +43,10 @@ class User < ApplicationRecord
favorites.map(&:party)
end
def admin?
role == 9
end
def blueprint
UserBlueprint
end

View file

@ -0,0 +1,5 @@
class AddRoleToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :role, :integer, default: 1, null: false
end
end

View file

@ -0,0 +1,5 @@
class AddVisibilityToParties < ActiveRecord::Migration[7.0]
def change
add_column :parties, :visibility, :integer, default: 1, null: false
end
end

File diff suppressed because it is too large Load diff