Authorisation management using CanCanCan added
This commit is contained in:
parent
51be4a4777
commit
7a3b65fe50
24 changed files with 211 additions and 295 deletions
3
Gemfile
3
Gemfile
|
|
@ -143,3 +143,6 @@ gem 'omniauth_openid_connect'
|
||||||
# Mitigate CVE-2015-9284
|
# Mitigate CVE-2015-9284
|
||||||
# https://github.com/cookpad/omniauth-rails_csrf_protection
|
# https://github.com/cookpad/omniauth-rails_csrf_protection
|
||||||
gem 'omniauth-rails_csrf_protection'
|
gem 'omniauth-rails_csrf_protection'
|
||||||
|
|
||||||
|
# Role-based access
|
||||||
|
gem 'cancancan'
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ GEM
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
byebug (11.1.3)
|
byebug (11.1.3)
|
||||||
|
cancancan (3.2.1)
|
||||||
capybara (3.35.3)
|
capybara (3.35.3)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
|
|
@ -430,6 +431,7 @@ DEPENDENCIES
|
||||||
bootsnap
|
bootsnap
|
||||||
byebug
|
byebug
|
||||||
bzip2-ruby!
|
bzip2-ruby!
|
||||||
|
cancancan
|
||||||
capybara
|
capybara
|
||||||
cookies_eu
|
cookies_eu
|
||||||
devise
|
devise
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,38 @@
|
||||||
class AdminController < ApplicationController
|
class AdminController < ApplicationController
|
||||||
before_action :authenticate_user!
|
# before_action :authenticate_user!
|
||||||
before_action :admin_only
|
# before_action :admin_only
|
||||||
|
|
||||||
def status
|
# def status
|
||||||
end
|
# end
|
||||||
|
|
||||||
def screenshots
|
# def screenshots
|
||||||
end
|
# end
|
||||||
|
|
||||||
def integration
|
# def integration
|
||||||
end
|
# end
|
||||||
|
|
||||||
def moderate_list
|
# def moderate_list
|
||||||
end
|
# end
|
||||||
|
|
||||||
def logs
|
# def logs
|
||||||
logs = Log
|
# logs = Log
|
||||||
|
|
||||||
if params[:search].present?
|
# if params[:search].present?
|
||||||
logger.debug "Searching for #{params[:search]}"
|
# logger.debug "Searching for #{params[:search]}"
|
||||||
logs = logs.where("message ilike ?", "%#{params[:search]}%")
|
# logs = logs.where("message ilike ?", "%#{params[:search]}%")
|
||||||
end
|
# end
|
||||||
|
|
||||||
@logs = logs.paginate(page: params[:page], per_page: 20)
|
# @logs = logs.paginate(page: params[:page], per_page: 20)
|
||||||
end
|
# end
|
||||||
|
|
||||||
|
|
||||||
private
|
# private
|
||||||
|
|
||||||
def admin_only
|
# def admin_only
|
||||||
unless current_user.is_admin?
|
# unless current_user.is_admin?
|
||||||
head :forbidden
|
# head :forbidden
|
||||||
# redirect_to :back, :alert => "Access denied."
|
# # redirect_to :back, :alert => "Access denied."
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base
|
||||||
# the navigation bar that contains a paginator of packages that contain
|
# the navigation bar that contains a paginator of packages that contain
|
||||||
# screenshots that require moderation.
|
# screenshots that require moderation.
|
||||||
def moderate_packages
|
def moderate_packages
|
||||||
if user_signed_in? and current_user.is_admin? and Package.need_moderation.any?
|
if can? :approve, Screenshot and Package.need_moderation.any?
|
||||||
@moderate_packages = Package.need_moderation
|
@moderate_packages = Package.need_moderation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,22 @@
|
||||||
class ModerateController < ApplicationController
|
# class ModerateController < ApplicationController
|
||||||
|
|
||||||
before_action :authenticate_user!
|
# before_action :authenticate_user!
|
||||||
|
|
||||||
def index
|
# def index
|
||||||
# First package with pending screenshots
|
# # First package with pending screenshots
|
||||||
@package = Package.joins(:screenshots).where('screenshots.approved=false or screenshots.markedfordelete=true').distinct(:name).first
|
# @package = Package.joins(:screenshots).where('screenshots.approved=false').distinct(:name).first
|
||||||
|
|
||||||
# # List of screenshots that were reported (to be removed)
|
# if @package
|
||||||
# @reported_screenshots = Screenshot.where(markedfordelete: true)
|
# # List of screenshots to be moderated
|
||||||
# # First package with reported screenshots
|
# @pending_screenshots = @package.screenshots_pending
|
||||||
# @reported_package = Package.joins(:screenshots).where('screenshots.markedfordelete=true').distinct(:name).first
|
# # Number of screenshots that were already moderated (during this session)
|
||||||
|
# session[:already_moderated] ||= 0
|
||||||
|
# @moderated_screenshots = session[:already_moderated]
|
||||||
|
|
||||||
if @package
|
# # BUG: calculation is wrong when pending and reported screenshots are in the queue
|
||||||
# List of screenshots to be moderated
|
# # Percentage of already moderated screenshots
|
||||||
@pending_screenshots = @package.screenshots_pending
|
# @percent_moderated = 100 * (@moderated_screenshots+1) / (@moderated_screenshots + @pending_screenshots.count)
|
||||||
# Number of screenshots that were already moderated (during this session)
|
# # @percent_moderated = 100 * (@moderated_screenshots+1) / (@moderated_screenshots + @pending_screenshots.count + @reported_screenshots.count)
|
||||||
session[:already_moderated] ||= 0
|
# end
|
||||||
@moderated_screenshots = session[:already_moderated]
|
# end
|
||||||
|
# end
|
||||||
# BUG: calculation is wrong when pending and reported screenshots are in the queue
|
|
||||||
# Percentage of already moderated screenshots
|
|
||||||
@percent_moderated = 100 * (@moderated_screenshots+1) / (@moderated_screenshots + @pending_screenshots.count)
|
|
||||||
# @percent_moderated = 100 * (@moderated_screenshots+1) / (@moderated_screenshots + @pending_screenshots.count + @reported_screenshots.count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,6 @@ class PackagesController < ApplicationController
|
||||||
# This action saves the screenshots already if they are valid. The user is
|
# This action saves the screenshots already if they are valid. The user is
|
||||||
# then given the chance to comment on and delete the screenshots again.
|
# then given the chance to comment on and delete the screenshots again.
|
||||||
def upload_receive
|
def upload_receive
|
||||||
# Create a pseudo user account for the user.
|
|
||||||
# The user won't know that an account is created.
|
|
||||||
# But this makes it easier to track who screenshots belong to.
|
|
||||||
# User.create_pseudo_user unless user_signed_in?
|
|
||||||
@package = Package.find_by!(name: params[:name])
|
@package = Package.find_by!(name: params[:name])
|
||||||
@valid_images = []
|
@valid_images = []
|
||||||
@invalid_images = []
|
@invalid_images = []
|
||||||
|
|
@ -65,7 +61,7 @@ class PackagesController < ApplicationController
|
||||||
|
|
||||||
# Check if the image was valid
|
# Check if the image was valid
|
||||||
if new_screenshot.valid?
|
if new_screenshot.valid?
|
||||||
new_screenshot.uploaderhash = session.id.to_s
|
# new_screenshot.uploaderhash = session.id.to_s
|
||||||
new_screenshot.uploaderip = request.remote_ip
|
new_screenshot.uploaderip = request.remote_ip
|
||||||
new_screenshot.version = @package.version
|
new_screenshot.version = @package.version
|
||||||
# ActiveStorage does not yet create a file checksum automatically.
|
# ActiveStorage does not yet create a file checksum automatically.
|
||||||
|
|
@ -77,19 +73,25 @@ class PackagesController < ApplicationController
|
||||||
Log.log "Duplicate image with fingerprint #{new_screenshot.image_fingerprint} found. Rejecting."
|
Log.log "Duplicate image with fingerprint #{new_screenshot.image_fingerprint} found. Rejecting."
|
||||||
all_errors << "Your file #{img.original_filename} is a duplicate. Sorry."
|
all_errors << "Your file #{img.original_filename} is a duplicate. Sorry."
|
||||||
else
|
else
|
||||||
# Can the upload get approved automatically?
|
# TODO: Can the upload get approved automatically?
|
||||||
if user_signed_in?
|
|
||||||
new_screenshot.user = current_user
|
|
||||||
new_screenshot.approve! if auto_approve?
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# Create a pseudo user account for the user.
|
||||||
|
# The user won't know that an account is created.
|
||||||
|
# But this makes it easier to track who screenshots belong to.
|
||||||
|
unless user_signed_in?
|
||||||
|
sign_in User.create_pseudo_user
|
||||||
|
end
|
||||||
|
new_screenshot.user = current_user
|
||||||
|
new_screenshot.approve! if can?(:approve, new_screenshot)
|
||||||
|
|
||||||
|
# Pre-render screenshot in different sizes
|
||||||
new_screenshot.simage_derivatives!
|
new_screenshot.simage_derivatives!
|
||||||
Log.log "Derivatives created"
|
Log.log "Derivatives created"
|
||||||
|
|
||||||
new_screenshot.save!
|
new_screenshot.save!
|
||||||
Log.log "Screenshot #{new_screenshot.id} uploaded successfully. " + \
|
Log.log "Screenshot #{new_screenshot.id} uploaded successfully. " + \
|
||||||
"ip=#{new_screenshot.uploaderip}. "+ \
|
"ip=#{new_screenshot.uploaderip}. "+ \
|
||||||
"user-hash=#{new_screenshot.uploaderhash}. "+ \
|
#"user-hash=#{new_screenshot.uploaderhash}. "+ \
|
||||||
"user-name=#{current_user} "+ \
|
"user-name=#{current_user} "+ \
|
||||||
"image-fingerprint=#{new_screenshot.image_fingerprint} "+ \
|
"image-fingerprint=#{new_screenshot.image_fingerprint} "+ \
|
||||||
"image-path=#{img.path}"
|
"image-path=#{img.path}"
|
||||||
|
|
@ -174,7 +176,7 @@ class PackagesController < ApplicationController
|
||||||
|
|
||||||
# Check if the user is allowed to change this screenshot
|
# Check if the user is allowed to change this screenshot
|
||||||
# - Is this the user's own screenshot? (anonymous)
|
# - Is this the user's own screenshot? (anonymous)
|
||||||
if @screenshot.user == current_user or current_user.is_admin?
|
if (@screenshot.user == current_user) || (can? :detroy, @screenshot)
|
||||||
logger.debug "User #{current_user} deletes screenshot #{@screenshot}"
|
logger.debug "User #{current_user} deletes screenshot #{@screenshot}"
|
||||||
@screenshot.destroy
|
@screenshot.destroy
|
||||||
flash['notice'] = "Screenshot deleted."
|
flash['notice'] = "Screenshot deleted."
|
||||||
|
|
@ -185,8 +187,9 @@ class PackagesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def approve_screenshot
|
def approve_screenshot
|
||||||
if current_user && current_user.is_admin?
|
@screenshot = Screenshot.find(params[:id])
|
||||||
@screenshot = Screenshot.find(params[:id])
|
|
||||||
|
if can? :approve, @screenshot
|
||||||
@screenshot.approve!
|
@screenshot.approve!
|
||||||
flash['notice'] = "Screenshot approved."
|
flash['notice'] = "Screenshot approved."
|
||||||
redirect_back(fallback_location: package_path(name: @screenshot.package.name))
|
redirect_back(fallback_location: package_path(name: @screenshot.package.name))
|
||||||
|
|
@ -263,23 +266,6 @@ class PackagesController < ApplicationController
|
||||||
redirect_back(fallback_location: package_path(name: @screenshot.package.name))
|
redirect_back(fallback_location: package_path(name: @screenshot.package.name))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Receive an anonymous report from a user to have a screenshot removed.
|
|
||||||
# def report_screenshot
|
|
||||||
# @screenshot = Screenshot.find(params[:id])
|
|
||||||
# # if verify_recaptcha
|
|
||||||
# @screenshot.delete_reason = params[:delete_reason]
|
|
||||||
# @screenshot.markedfordelete = true
|
|
||||||
# if @screenshot.valid?
|
|
||||||
# @screenshot.save!
|
|
||||||
# flash['notice'] = "Screenshot reported. The moderators will deal with it."
|
|
||||||
# else
|
|
||||||
# errors = @screenshot.errors.to_a.join(' and ')
|
|
||||||
# flash['alert'] = "Sorry. #{errors}"
|
|
||||||
# end
|
|
||||||
# # end
|
|
||||||
# redirect_back(fallback_location: package_path)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Show an HTML partial with reviews of this package from the Ubuntu API
|
# Show an HTML partial with reviews of this package from the Ubuntu API
|
||||||
# def reviews
|
# def reviews
|
||||||
# expires_in 1.day, public: true
|
# expires_in 1.day, public: true
|
||||||
|
|
@ -350,37 +336,7 @@ class PackagesController < ApplicationController
|
||||||
params.require(:screenshot).permit(:description)
|
params.require(:screenshot).permit(:description)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Do uploads from this user get approved automatically?
|
|
||||||
def auto_approve?
|
|
||||||
# Anonymous users need to go through moderation
|
|
||||||
return false unless user_signed_in?
|
|
||||||
|
|
||||||
# Admins do not need moderation
|
|
||||||
return true if current_user.is_admin?
|
|
||||||
|
|
||||||
# Debian developers do not need moderation
|
|
||||||
return true if current_user.provider == 'debian-sso'
|
|
||||||
|
|
||||||
# After one successfully approved screenshot users do not need moderation
|
|
||||||
# return true if current_user.approved_screenshots.count > 0
|
|
||||||
|
|
||||||
# Any other user's upload must be moderated
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
def screenshots_visible_to_user(package)
|
def screenshots_visible_to_user(package)
|
||||||
if user_signed_in? and current_user.is_admin?
|
package.screenshots.accessible_by(current_ability, :view).order('created_at DESC')
|
||||||
# User is an admin and can view all screenshots
|
|
||||||
package.screenshots
|
|
||||||
# TODO: User logins and assigned screenshots will come in a later version
|
|
||||||
# elsif user_signed_in?
|
|
||||||
# package.screenshots.where(id: (current_user.screenshots.select(:id))) | \
|
|
||||||
# package.screenshots.where(approved: true).order('created_at DESC')
|
|
||||||
else
|
|
||||||
package.screenshots.where(uploaderhash: session.id.to_s).or(
|
|
||||||
package.screenshots.where(approved: true)
|
|
||||||
)
|
|
||||||
.order('created_at DESC')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,5 @@
|
||||||
module PackagesHelper
|
module PackagesHelper
|
||||||
|
|
||||||
# Return a query of all screenshots that the current user may see
|
|
||||||
# Consists of:
|
|
||||||
# - approved (public) screenshots
|
|
||||||
# - screenshots uploaded by the user
|
|
||||||
# - all screenshots if the user is an admin
|
|
||||||
# def screenshots_visible_to_user(package)
|
|
||||||
# if user_signed_in? and current_user.is_admin?
|
|
||||||
# # User is an admin and can view all screenshots
|
|
||||||
# package.screenshots
|
|
||||||
# # TODO: User logins and assigned screenshots will come in a later version
|
|
||||||
# # elsif user_signed_in?
|
|
||||||
# # package.screenshots.where(id: (current_user.screenshots.select(:id))) | \
|
|
||||||
# # package.screenshots.where(approved: true).order('created_at DESC')
|
|
||||||
# else
|
|
||||||
# package.screenshots.where(uploaderhash: session.id.to_s) | \
|
|
||||||
# package.screenshots.where(approved: true).order('created_at DESC')
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
def screenshots_visible_to_user(package, user_session)
|
|
||||||
# Show all screenshots to admins
|
|
||||||
if current_user && current_user.is_admin?
|
|
||||||
package.screenshots
|
|
||||||
|
|
||||||
# Show screenshots belonging to the user or public/approved
|
|
||||||
else
|
|
||||||
package.screenshots.where(
|
|
||||||
uploaderhash: user_session.id.to_s
|
|
||||||
).or(
|
|
||||||
package.screenshots.where(
|
|
||||||
approved: true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def screenshot_uploaded_by_current_user?(screenshot)
|
|
||||||
screenshot.uploaderhash == session.id.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the description and/or the version of the package
|
# Return the description and/or the version of the package
|
||||||
def screenshot_caption(screenshot)
|
def screenshot_caption(screenshot)
|
||||||
str = []
|
str = []
|
||||||
|
|
@ -78,9 +38,7 @@ module PackagesHelper
|
||||||
|
|
||||||
def status_text(screenshot)
|
def status_text(screenshot)
|
||||||
# TODO: markedfordelete/reporting will be removed
|
# TODO: markedfordelete/reporting will be removed
|
||||||
if screenshot.markedfordelete
|
if screenshot.approved
|
||||||
"Removal requested > #{screenshot.delete_reason}"
|
|
||||||
elsif screenshot.approved
|
|
||||||
fa_icon('check-square') + ' Public'
|
fa_icon('check-square') + ' Public'
|
||||||
else
|
else
|
||||||
fa_icon('hourglass') + ' Waiting for approval'
|
fa_icon('hourglass') + ' Waiting for approval'
|
||||||
|
|
|
||||||
56
app/models/ability.rb
Normal file
56
app/models/ability.rb
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Ability
|
||||||
|
include CanCan::Ability
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
|
||||||
|
# Define abilities for the passed in user here. For example:
|
||||||
|
#
|
||||||
|
# user ||= User.new # guest user (not logged in)
|
||||||
|
# if user.admin?
|
||||||
|
# can :manage, :all
|
||||||
|
# else
|
||||||
|
# can :read, :all
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# The first argument to `can` is the action you are giving the user
|
||||||
|
# permission to do.
|
||||||
|
# If you pass :manage it will apply to every action. Other common actions
|
||||||
|
# here are :read, :create, :update and :destroy.
|
||||||
|
#
|
||||||
|
# The second argument is the resource the user can perform the action on.
|
||||||
|
# If you pass :all it will apply to every resource. Otherwise pass a Ruby
|
||||||
|
# class of the resource.
|
||||||
|
#
|
||||||
|
# The third argument is an optional hash of conditions to further filter the
|
||||||
|
# objects.
|
||||||
|
# For example, here the user can only update published articles.
|
||||||
|
#
|
||||||
|
# can :update, Article, :published => true
|
||||||
|
#
|
||||||
|
# See the wiki for details:
|
||||||
|
# https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
|
||||||
|
|
||||||
|
if user.present? # Logged-in users
|
||||||
|
if user.admin_role?
|
||||||
|
can :approve, Screenshot
|
||||||
|
can :destroy, Screenshot
|
||||||
|
can :destroy, User
|
||||||
|
can :view, Screenshot
|
||||||
|
can :destroy, Package
|
||||||
|
end
|
||||||
|
if user.moderator_role?
|
||||||
|
can :approve, Screenshot
|
||||||
|
can :view, Screenshot
|
||||||
|
can :destroy, Screenshot
|
||||||
|
end
|
||||||
|
if user.pseudo?
|
||||||
|
# Allow to view all public/approved screenshots
|
||||||
|
can :view, Screenshot, approved: true
|
||||||
|
# Allow to view any own uploads (even not-yet-approved)
|
||||||
|
can :view, Screenshot, user_id: user.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -53,7 +53,7 @@ class Package < ApplicationRecord
|
||||||
|
|
||||||
# Return a list of packages that have screenshots to be moderated
|
# Return a list of packages that have screenshots to be moderated
|
||||||
def self.need_moderation
|
def self.need_moderation
|
||||||
Package.joins(:screenshots).where('screenshots.approved=false or screenshots.markedfordelete=true').distinct(:name)
|
Package.joins(:screenshots).where('screenshots.approved=false').distinct(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ class Package < ApplicationRecord
|
||||||
|
|
||||||
# Return a query of all approved/public screenshots of this package
|
# Return a query of all approved/public screenshots of this package
|
||||||
def screenshots_pending
|
def screenshots_pending
|
||||||
self.screenshots.where('approved=false or markedfordelete=true')
|
self.screenshots.where('approved=false')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return a list of packages that have unapproved screenshots
|
# Return a list of packages that have unapproved screenshots
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,6 @@ class Screenshot < ApplicationRecord
|
||||||
# Shrine
|
# Shrine
|
||||||
include ImageUploader::Attachment(:simage) # adds an `simage` virtual attribute
|
include ImageUploader::Attachment(:simage) # adds an `simage` virtual attribute
|
||||||
|
|
||||||
# Calculate how many days ago this screenshot has been uploaded
|
|
||||||
def age
|
|
||||||
time_ago_in_words(self.created_at)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return caption for full-screen screenshots.
|
# Return caption for full-screen screenshots.
|
||||||
# Takes the description of a screenshot if available.
|
# Takes the description of a screenshot if available.
|
||||||
# Otherwise it falls back to the general description of its package.
|
# Otherwise it falls back to the general description of its package.
|
||||||
|
|
@ -54,19 +49,16 @@ class Screenshot < ApplicationRecord
|
||||||
|
|
||||||
# Brief text describing the status of this screenshots (for admins)
|
# Brief text describing the status of this screenshots (for admins)
|
||||||
def adminstatus
|
def adminstatus
|
||||||
if self.markedfordelete
|
if self.approved
|
||||||
"Removal requested > #{self.delete_reason}"
|
|
||||||
elsif self.approved
|
|
||||||
'Public'
|
'Public'
|
||||||
else
|
else
|
||||||
fa_icon('hourglass') + 'Waiting for approval'
|
#fa_icon('hourglass') + 'Waiting for approval'
|
||||||
|
'Waiting for approval'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Publish a screenshot from the moderation queue
|
# Publish a screenshot from the moderation queue
|
||||||
def approve!
|
def approve!
|
||||||
self.delete_reason = nil
|
|
||||||
self.markedfordelete = false
|
|
||||||
self.approved = true
|
self.approved = true
|
||||||
self.save!
|
self.save!
|
||||||
end
|
end
|
||||||
|
|
@ -76,35 +68,6 @@ class Screenshot < ApplicationRecord
|
||||||
self.order(created_at: :desc).where(approved: true).first
|
self.order(created_at: :desc).where(approved: true).first
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the current user has administrative permissions
|
|
||||||
# def can_admin?
|
|
||||||
# self.admin == 1
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Returns true if the current user can upload screenshots without moderation
|
|
||||||
# def can_upload_without_moderation?
|
|
||||||
# # Admins can upload without moderation
|
|
||||||
# true if self.can_admin
|
|
||||||
|
|
||||||
# # Authenticated users with at least one approved screenshot
|
|
||||||
# #true if self.screenshots.where(approved: true).count >= 1
|
|
||||||
|
|
||||||
# # Other visitors require moderation
|
|
||||||
# false
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Returns true if the current user can delete screenshots
|
|
||||||
# def can_delete?
|
|
||||||
# # Admins can delete screenshots
|
|
||||||
# true if self.can_admin
|
|
||||||
|
|
||||||
# # Authenticated users with at least one approved screenshot
|
|
||||||
# #true if self.screenshots.where(approved: true).count >= 1
|
|
||||||
|
|
||||||
# # Other visitors require moderation
|
|
||||||
# false
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Return the part of the version up to the first - or +
|
# Return the part of the version up to the first - or +
|
||||||
def upstream_version
|
def upstream_version
|
||||||
self.version.split(/[\-\+]/).first
|
self.version.split(/[\-\+]/).first
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,6 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_admin?
|
|
||||||
self.admin == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if a user has been created on-the-fly and is just an
|
# Check if a user has been created on-the-fly and is just an
|
||||||
# anonymous user who uploaded a screenshot. They can turn this
|
# anonymous user who uploaded a screenshot. They can turn this
|
||||||
# user record into a registered account though.
|
# user record into a registered account though.
|
||||||
|
|
@ -59,6 +55,11 @@ class User < ApplicationRecord
|
||||||
user.name = auth.info.name
|
user.name = auth.info.name
|
||||||
# Set a random password
|
# Set a random password
|
||||||
user.password = Devise.friendly_token[0,20]
|
user.password = Devise.friendly_token[0,20]
|
||||||
|
|
||||||
|
# Users coming through salsa.debian.net/OpenID-Connect will get moderator role
|
||||||
|
if user.email.end_with?('@debian.org') && user.provider=='salsa'
|
||||||
|
user.moderator_role = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -66,20 +67,18 @@ class User < ApplicationRecord
|
||||||
self.screenshots.where(approved: true)
|
self.screenshots.where(approved: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Seamlessly create a user account for the current client.
|
# Seamlessly create a user account for the current visitor.
|
||||||
# It helps track uploads because uploaded screenshots get assigned
|
# It helps track uploads because uploaded screenshots get assigned
|
||||||
# to this user record. The user can later decide to use a real
|
# to this user record.
|
||||||
# account and get their screenshots transferred to that.
|
# TODO: The user can later decide to use a real account and get their screenshots transferred to that.
|
||||||
# def self.create_pseudo_user
|
def self.create_pseudo_user
|
||||||
# generated_password = Devise.friendly_token.first(8)
|
generated_password = Devise.friendly_token.first(8)
|
||||||
# new_user = User.create(
|
new_user = User.create(
|
||||||
# name: 'Anonymous',
|
name: 'Anonymous',
|
||||||
# password: generated_password)
|
password: generated_password,
|
||||||
# Log.log "New pseudo user for anonymous upload created: #{new_user}"
|
pseudo: true
|
||||||
# sign_in new_user
|
)
|
||||||
# end
|
Log.log "New pseudo user for anonymous upload created: #{new_user}"
|
||||||
#
|
return new_user
|
||||||
# Currently the approach is different: do not create an account
|
end
|
||||||
# for a user. Instead store the IDs of the uploaded screenshots
|
|
||||||
# and transfer them if the user decides to do a real login using SSO.
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,3 @@
|
||||||
/ ' | Cookie is #{session[:token]}
|
/ ' | Cookie is #{session[:token]}
|
||||||
|
|
||||||
= render 'cookies_eu/consent_banner', link: '/about#cookies'
|
= render 'cookies_eu/consent_banner', link: '/about#cookies'
|
||||||
|
|
||||||
/ = current_user.is_admin?
|
|
||||||
/ = session.id
|
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,6 @@
|
||||||
a.button.alert.round href=delete_screenshot_path(screenshot) onclick="return confirm('Really delete the screenshot?');" Reject
|
a.button.alert.round href=delete_screenshot_path(screenshot) onclick="return confirm('Really delete the screenshot?');" Reject
|
||||||
a.button.success.round href=approve_screenshot_path(screenshot) Approve
|
a.button.success.round href=approve_screenshot_path(screenshot) Approve
|
||||||
|
|
||||||
/ Existing screenshots that was requested for removal
|
|
||||||
- if screenshot.markedfordelete==true
|
|
||||||
.button-group
|
|
||||||
a.button.alert.round href=delete_screenshot_path(screenshot) onclick="return confirm('Really delete the screenshot?');" Remove as requested
|
|
||||||
a.button.success.round href=approve_screenshot_path(screenshot) Keep the screenshot
|
|
||||||
|
|
||||||
|
|
||||||
.small-6.cell
|
.small-6.cell
|
||||||
p Existing screenshots:
|
p Existing screenshots:
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
= link_to moderate_list_path
|
= link_to moderate_list_path
|
||||||
= fa_icon 'check 2x', text: 'Moderate'
|
= fa_icon 'check 2x', text: 'Moderate'
|
||||||
// Add link if user has uploaded screenshots
|
// Add link if user has uploaded screenshots
|
||||||
- if @current_users_screenshots and @current_users_screenshots.any?
|
/ - if @current_users_screenshots and @current_users_screenshots.any?
|
||||||
li class=('active' if controller_name=='my' and action_name=='uploads')
|
/ li class=('active' if controller_name=='my' and action_name=='uploads')
|
||||||
= link_to my_uploads_path
|
/ = link_to my_uploads_path
|
||||||
= fa_icon 'image 2x', text: 'My uploads'
|
/ = fa_icon 'image 2x', text: 'My uploads'
|
||||||
' My uploads
|
/ ' My uploads
|
||||||
|
|
||||||
li
|
li
|
||||||
= link_to destroy_user_session_path, method: :delete
|
= link_to destroy_user_session_path, method: :delete
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@
|
||||||
h2 Number of screenshots you uploaded
|
h2 Number of screenshots you uploaded
|
||||||
p = current_user.screenshots.count
|
p = current_user.screenshots.count
|
||||||
|
|
||||||
- if current_user.is_admin?
|
/ - if current_user.is_admin?
|
||||||
h2 Admin
|
/ h2 Admin
|
||||||
p Apparently you are an administrator. Be careful with that thing!
|
/ p Apparently you are an administrator. Be careful with that thing!
|
||||||
|
/ - you can moderate
|
||||||
tt = current_user.inspect
|
/ - you can …
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
- @current_users_screenshots.each do |screenshot|
|
- @current_users_screenshots.each do |screenshot|
|
||||||
.column
|
.column
|
||||||
/ TODO: old Paperclip style
|
/ TODO: old Paperclip style
|
||||||
a.black.fancybox href=screenshot.image.variant(resize_to_limit: [800,600], timestamp: false)
|
a.black.fancybox href=screenshot.simage_url(:large)
|
||||||
div.grid-thumbnail
|
div.grid-thumbnail
|
||||||
= image_tag(screenshot.image.variant(resize_to_limit: [160,120]), alt: screenshot.caption, class: 'thumbnail')
|
= image_tag(screenshot.simage_url(:medium), alt: screenshot.caption, class: 'thumbnail')
|
||||||
div
|
div
|
||||||
= screenshot.description
|
= screenshot.description
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
// Button that reveals a dropdown/modal for moderation/reporting
|
// Button that reveals a dropdown/modal for moderation/reporting
|
||||||
|
|
||||||
|
- if can?(:destroy, screenshot) || can?(:approve, screenshot)
|
||||||
|
.text
|
||||||
|
span.label.secondary
|
||||||
|
= status_text(screenshot)
|
||||||
|
|
||||||
.button-group.small.align-center
|
.button-group.small.align-center
|
||||||
|
|
||||||
- if (screenshot_uploaded_by_current_user?(screenshot) || (current_user && current_user.is_admin?))
|
- if can?(:destroy, screenshot)
|
||||||
a.button.small.bordered.radius.alert[
|
a.button.small.bordered.radius.alert[
|
||||||
href=delete_screenshot_path(screenshot.id)
|
href=delete_screenshot_path(screenshot.id)
|
||||||
onclick="return confirm('Really delete the screenshot?');"
|
onclick="return confirm('Really delete the screenshot?');"
|
||||||
|
|
@ -15,29 +21,15 @@
|
||||||
/ href='#'
|
/ href='#'
|
||||||
/ ] #{fa_icon 'star'} Make primary
|
/ ] #{fa_icon 'star'} Make primary
|
||||||
|
|
||||||
- if not screenshot.approved
|
- unless screenshot.approved
|
||||||
- if current_user && current_user.is_admin?
|
- if can?(:approve, screenshot)
|
||||||
a.button.small.success[
|
a.button.small.success[
|
||||||
href=approve_screenshot_path(screenshot.id)
|
href=approve_screenshot_path(screenshot.id)
|
||||||
method='post'
|
method='post'
|
||||||
] Approve
|
] #{fa_icon 'check'} Approve
|
||||||
|
|
||||||
/ - elsif screenshot.markedfordelete
|
|
||||||
/ a.button.small.success[
|
|
||||||
/ href=approve_screenshot_path(screenshot.id)
|
|
||||||
/ method='post'
|
|
||||||
/ ] Keep
|
|
||||||
|
|
||||||
|
|
||||||
/ Display additional information to admins
|
/ Display additional information to admins
|
||||||
p Status: #{screenshot.adminstatus}
|
/ p Status: #{screenshot.adminstatus}
|
||||||
p Uploader IP=#{screenshot.uploaderip}
|
/ p Uploader IP=#{screenshot.uploaderip}
|
||||||
p Uploader Token=#{session[:token]}
|
/ p Uploader Token=#{session[:token]}
|
||||||
p Uploaded #{screenshot.age} ago (#{screenshot.created_at})
|
/ p Uploaded #{time_ago_in_words(screenshot.created_at)} ago (#{screenshot.created_at})
|
||||||
|
|
||||||
/ TODO: Move reporting screenshots to an extra page with a form and captcha
|
|
||||||
/ - else
|
|
||||||
/ / Allow anonymous users to report inappropriate screenshots
|
|
||||||
/ = form_tag(report_screenshot_path(screenshot.id))
|
|
||||||
/ = text_area_tag 'delete_reason', nil, class: 'input-group-field', maxlength: 100, rows: 3, cols: 50
|
|
||||||
/ = submit_tag 'Request removal', class: 'button alert'
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// Button that reveals a dropdown/modal for users (for their own screenshots)
|
|
||||||
.text-right
|
|
||||||
button.small.dropdown.warning.button type="button" data-toggle="admin-info-#{screenshot.id}"
|
|
||||||
'Request removal
|
|
||||||
.dropdown-pane data-dropdown=true id="admin-info-#{screenshot.id}"
|
|
||||||
p If you think this screenshot does not meet the guidelines
|
|
||||||
or is misleading or not useful then please report it so that
|
|
||||||
the moderators can review it. Just let us know why you think
|
|
||||||
the screenshot should get removed:
|
|
||||||
= form_tag(report_screenshot_path(screenshot.id))
|
|
||||||
= text_area_tag 'delete_reason', nil, class: 'input-group-field', maxlength: 100, rows: 3, cols: 50
|
|
||||||
= submit_tag 'Request removal', class: 'button alert'
|
|
||||||
|
|
@ -25,24 +25,13 @@
|
||||||
.text
|
.text
|
||||||
= screenshot.caption
|
= screenshot.caption
|
||||||
// Has the unmoderated screenshot been uploaded by the current user?
|
// Has the unmoderated screenshot been uploaded by the current user?
|
||||||
- if screenshot_uploaded_by_current_user?(screenshot)
|
- if current_user.screenshots.include?(screenshot)
|
||||||
.text
|
.text
|
||||||
span.label.secondary
|
span.label.secondary
|
||||||
' Uploaded by you
|
' Uploaded by you
|
||||||
- unless screenshot.approved
|
- unless screenshot.approved
|
||||||
' (needs to be approved)
|
' (needs to be approved)
|
||||||
= render(partial: 'admin_buttons', locals: {screenshot: screenshot})
|
= render(partial: 'admin_buttons', locals: {screenshot: screenshot})
|
||||||
// Is the user an admin?
|
|
||||||
// or does the screenshot belong to the user (determined by session cookie)
|
|
||||||
- if current_user && current_user.is_admin?
|
|
||||||
hr
|
|
||||||
.text
|
|
||||||
span.label.secondary
|
|
||||||
= status_text(screenshot)
|
|
||||||
|
|
||||||
= render(partial: 'admin_buttons', locals: {screenshot: screenshot})
|
|
||||||
/ - elsif user_signed_in? and screenshot.user == current_user
|
|
||||||
/ = render(partial: 'user_dropdown', locals: {screenshot: screenshot})
|
|
||||||
|
|
||||||
|
|
||||||
// Second paginator at the bottom so the user does not have to scroll up again
|
// Second paginator at the bottom so the user does not have to scroll up again
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
class DropDeprecatedScreenshotsFields < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
remove_column :screenshots, :markedfordelete
|
||||||
|
remove_column :screenshots, :delete_reason
|
||||||
|
remove_column :screenshots, :image_file_name
|
||||||
|
remove_column :screenshots, :image_content_type
|
||||||
|
remove_column :screenshots, :image_file_size
|
||||||
|
remove_column :screenshots, :image_updated_at
|
||||||
|
end
|
||||||
|
end
|
||||||
14
db/migrate/20210228100636_add_roles_to_users.rb
Normal file
14
db/migrate/20210228100636_add_roles_to_users.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Rename the :admin field to :admin_role and change integer to boolean
|
||||||
|
class AddRolesToUsers < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :users, :admin_role, :boolean, default: false
|
||||||
|
|
||||||
|
User.find_each do |user|
|
||||||
|
user.admin_role=true if user.admin>0
|
||||||
|
user.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_column :users, :admin
|
||||||
|
add_column :users, :moderator_role, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
||||||
8
db/migrate/20210228191717_add_pseudo_column_to_user.rb
Normal file
8
db/migrate/20210228191717_add_pseudo_column_to_user.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Create a pseudo flag for accounts that get automatically created.
|
||||||
|
# If an anonymous visitor uploads a screenshot he will get a
|
||||||
|
# pseudo account to assign the screenshots to.
|
||||||
|
class AddPseudoColumnToUser < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :users, :pseudo, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
||||||
12
db/schema.rb
12
db/schema.rb
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2021_02_22_102719) do
|
ActiveRecord::Schema.define(version: 2021_02_28_191717) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
@ -88,14 +88,8 @@ ActiveRecord::Schema.define(version: 2021_02_22_102719) do
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.string "uploaderhash", limit: 72
|
t.string "uploaderhash", limit: 72
|
||||||
t.boolean "approved", default: false, null: false
|
t.boolean "approved", default: false, null: false
|
||||||
t.boolean "markedfordelete"
|
|
||||||
t.string "delete_reason", limit: 100
|
|
||||||
t.text "description"
|
t.text "description"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.string "image_file_name"
|
|
||||||
t.string "image_content_type"
|
|
||||||
t.integer "image_file_size"
|
|
||||||
t.datetime "image_updated_at"
|
|
||||||
t.string "image_fingerprint"
|
t.string "image_fingerprint"
|
||||||
t.integer "user_id", default: 0
|
t.integer "user_id", default: 0
|
||||||
t.text "simage_data"
|
t.text "simage_data"
|
||||||
|
|
@ -120,7 +114,9 @@ ActiveRecord::Schema.define(version: 2021_02_22_102719) do
|
||||||
t.datetime "locked_at"
|
t.datetime "locked_at"
|
||||||
t.string "provider"
|
t.string "provider"
|
||||||
t.string "uid"
|
t.string "uid"
|
||||||
t.integer "admin", default: 0
|
t.boolean "admin_role", default: false
|
||||||
|
t.boolean "moderator_role", default: false
|
||||||
|
t.boolean "pseudo", default: false
|
||||||
t.index ["email", "provider"], name: "index_users_on_email_and_provider", unique: true
|
t.index ["email", "provider"], name: "index_users_on_email_and_provider", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
4
test/fixtures/screenshots.yml
vendored
4
test/fixtures/screenshots.yml
vendored
|
|
@ -6,8 +6,6 @@
|
||||||
# uploaderhash: MyString
|
# uploaderhash: MyString
|
||||||
# uploaderip: MyString
|
# uploaderip: MyString
|
||||||
# approved:
|
# approved:
|
||||||
# markedfordelete:
|
|
||||||
# delete_reason: MyString
|
|
||||||
# description: MyString
|
# description: MyString
|
||||||
|
|
||||||
# two:
|
# two:
|
||||||
|
|
@ -16,6 +14,4 @@
|
||||||
# uploaderhash: MyString
|
# uploaderhash: MyString
|
||||||
# uploaderip: MyString
|
# uploaderip: MyString
|
||||||
# approved:
|
# approved:
|
||||||
# markedfordelete:
|
|
||||||
# delete_reason: MyString
|
|
||||||
# description: MyString
|
# description: MyString
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue