This was a big commit! I included lots of documentation below, but generally I

did a few things. I wrote many many many new tests, either in old test files or
in the three new test files I made. I also did some code-keeping work, deleting
trailing whitespace and deleting vestigial code. Lastly, I fixed the parts of
the code which I realized were broken thru the process of running tests.

===============================================================================
 Deleted trailing whitespace:
===============================================================================
--\  mediagoblin/decorators.py
--\  mediagoblin/auth/tools.py
--\  mediagoblin/db/migrations.py
--\  mediagoblin/db/models.py
--\  mediagoblin/gmg_commands/users.py
--\  mediagoblin/moderation/forms.py
--\  mediagoblin/moderation/tools.py
--\  mediagoblin/moderation/views.py
--\  mediagoblin/templates/mediagoblin/moderation/media_panel.html
--\  mediagoblin/templates/mediagoblin/moderation/report.html
--\  mediagoblin/templates/mediagoblin/moderation/report_panel.html
--\  mediagoblin/templates/mediagoblin/moderation/user.html
--\  mediagoblin/templates/mediagoblin/moderation/user_panel.html
--\  mediagoblin/templates/mediagoblin/user_pages/report.html
--\  mediagoblin/templates/mediagoblin/utils/report.html
--\  mediagoblin/user_pages/lib.py
--\  mediagoblin/user_pages/views.py
===============================================================================
 Deleted Vestigial Code
===============================================================================
--\  mediagoblin/db/util.py
--\  mediagoblin/tests/test_notifications.py
===============================================================================
 Modified the Code:
===============================================================================
--\  mediagoblin/moderation/tools.py
--| Encapsulated the code around giving/taking away privileges into two
  | funtions.

--\  mediagoblin/moderation/views.py
--| Imported and used the give/take away privilege functions
--| Replaced 'require_admin_or_moderator_login' with
  |'user_has_privilege(u"admin")' for adding/taking away privileges, only
  | admins are allowed to do this.

--\  mediagoblin/templates/mediagoblin/banned.html
--| Added relevant translation tags
--| Added ability to display indefinite banning

--\  mediagoblin/templates/mediagoblin/user_pages/media.html
--| Made sure the add comments button was only visible for users with the
  | `commenter` privilege

--\  mediagoblin/tests/test_submission.py
--| Paroneayea fixed a DetachedInstanceError I was having with the our_user
  | function

--\  mediagoblin/tests/tools.py
--| Added a fixture_add_comment_report function for testing.

--\  mediagoblin/tools/response.py
--| Fixed a minor error where a necessary return statement was missing
--| Fit the code within 80 columns

--\  mediagoblin/user_pages/views.py
--| Added a necessary decorator to ensure that only users with the 'commenter'
  | privilege can post comments
===============================================================================
 Wrote new tests for an old test file:
===============================================================================
--\  mediagoblin/tests/test_auth.py
--| Added a new test to make sure privilege granting on registration happens
  | correctly

--\  mediagoblin/tests/test_modelmethods.py*
--| Added a test to ensure the User method has_privilege works properly
===============================================================================
 Wrote entirely new files full of tests:
===============================================================================
--\  mediagoblin/tests/test_moderation.py
--\  mediagoblin/tests/test_privileges.py
--\  mediagoblin/tests/test_reporting.py
===============================================================================
===============================================================================
NOTE: Any files I've marked with a * in this commit report, were actually subm-
itted in my last commit. I made that committ to fix an error I was having, so
they weren't properly documented in that report.
===============================================================================
===============================================================================
This commit is contained in:
tilly-Q 2013-08-29 13:47:50 -04:00
parent e46fb71c1d
commit dfd66b789c
28 changed files with 793 additions and 157 deletions

View File

@ -164,13 +164,13 @@ def register_user(request, register_form):
user = auth.create_user(register_form) user = auth.create_user(register_form)
# give the user the default privileges # give the user the default privileges
default_privileges = [ default_privileges = [
Privilege.query.filter(Privilege.privilege_name==u'commenter').first(), Privilege.query.filter(Privilege.privilege_name==u'commenter').first(),
Privilege.query.filter(Privilege.privilege_name==u'uploader').first(), Privilege.query.filter(Privilege.privilege_name==u'uploader').first(),
Privilege.query.filter(Privilege.privilege_name==u'reporter').first()] Privilege.query.filter(Privilege.privilege_name==u'reporter').first()]
user.all_privileges += default_privileges user.all_privileges += default_privileges
user.save() user.save()
# log the user in # log the user in
request.session['user_id'] = unicode(user.id) request.session['user_id'] = unicode(user.id)
request.session.save() request.session.save()

View File

@ -28,7 +28,7 @@ from migrate.changeset.constraint import UniqueConstraint
from mediagoblin.db.extratypes import JSONEncoded from mediagoblin.db.extratypes import JSONEncoded
from mediagoblin.db.migration_tools import RegisterMigration, inspect_table from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
from mediagoblin.db.models import (MediaEntry, Collection, User, from mediagoblin.db.models import (MediaEntry, Collection, User,
MediaComment, Privilege, ReportBase) MediaComment, Privilege, ReportBase)
MIGRATIONS = {} MIGRATIONS = {}
@ -425,7 +425,7 @@ class RequestToken_v0(declarative_base()):
callback = Column(Unicode, nullable=False, default=u"oob") callback = Column(Unicode, nullable=False, default=u"oob")
created = Column(DateTime, nullable=False, default=datetime.datetime.now) created = Column(DateTime, nullable=False, default=datetime.datetime.now)
updated = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
class AccessToken_v0(declarative_base()): class AccessToken_v0(declarative_base()):
""" """
Model for representing the access tokens Model for representing the access tokens
@ -438,7 +438,7 @@ class AccessToken_v0(declarative_base()):
request_token = Column(Unicode, ForeignKey(RequestToken_v0.token)) request_token = Column(Unicode, ForeignKey(RequestToken_v0.token))
created = Column(DateTime, nullable=False, default=datetime.datetime.now) created = Column(DateTime, nullable=False, default=datetime.datetime.now)
updated = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
class NonceTimestamp_v0(declarative_base()): class NonceTimestamp_v0(declarative_base()):
""" """
@ -467,7 +467,7 @@ class ReportBase_v0(declarative_base()):
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
report_content = Column(UnicodeText) report_content = Column(UnicodeText)
reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.now) created = Column(DateTime, nullable=False, default=datetime.datetime.now)
discriminator = Column('type', Unicode(50)) discriminator = Column('type', Unicode(50))
__mapper_args__ = {'polymorphic_on': discriminator} __mapper_args__ = {'polymorphic_on': discriminator}
@ -512,14 +512,14 @@ class Privilege_v0(declarative_base()):
class PrivilegeUserAssociation_v0(declarative_base()): class PrivilegeUserAssociation_v0(declarative_base()):
__tablename__ = 'core__privileges_users' __tablename__ = 'core__privileges_users'
group_id = Column( group_id = Column(
'core__privilege_id', 'core__privilege_id',
Integer, Integer,
ForeignKey(User.id), ForeignKey(User.id),
primary_key=True) primary_key=True)
user_id = Column( user_id = Column(
'core__user_id', 'core__user_id',
Integer, Integer,
ForeignKey(Privilege.id), ForeignKey(Privilege.id),
primary_key=True) primary_key=True)
@RegisterMigration(15, MIGRATIONS) @RegisterMigration(15, MIGRATIONS)

View File

@ -669,7 +669,7 @@ class ReportBase(Base):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
reporter = relationship( reporter = relationship(
User, User,
backref=backref("reports_filed_by", backref=backref("reports_filed_by",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan"), cascade="all, delete-orphan"),
@ -677,7 +677,7 @@ class ReportBase(Base):
report_content = Column(UnicodeText) report_content = Column(UnicodeText)
reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
reported_user = relationship( reported_user = relationship(
User, User,
backref=backref("reports_filed_on", backref=backref("reports_filed_on",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan"), cascade="all, delete-orphan"),
@ -722,7 +722,7 @@ class MediaReport(ReportBase):
primary_key=True) primary_key=True)
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
media_entry = relationship( media_entry = relationship(
MediaEntry, MediaEntry,
backref=backref("reports_filed_onmod/reports/1/", backref=backref("reports_filed_onmod/reports/1/",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan")) cascade="all, delete-orphan"))
@ -738,7 +738,7 @@ class ArchivedReport(ReportBase):
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id)) media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
media_entry = relationship( media_entry = relationship(
MediaEntry, MediaEntry,
backref=backref("past_reports_filed_on", backref=backref("past_reports_filed_on",
lazy="dynamic")) lazy="dynamic"))
comment_id = Column(Integer, ForeignKey(MediaComment.id)) comment_id = Column(Integer, ForeignKey(MediaComment.id))
@ -748,7 +748,7 @@ class ArchivedReport(ReportBase):
resolver_id = Column(Integer, ForeignKey(User.id), nullable=False) resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
resolver = relationship( resolver = relationship(
User, User,
backref=backref("reports_resolved_by", backref=backref("reports_resolved_by",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan"), cascade="all, delete-orphan"),
@ -759,23 +759,23 @@ class ArchivedReport(ReportBase):
class UserBan(Base): class UserBan(Base):
""" """
Holds the information on a specific user's ban-state. As long as one of Holds the information on a specific user's ban-state. As long as one of
these is attached to a user, they are banned from accessing mediagoblin. these is attached to a user, they are banned from accessing mediagoblin.
When they try to log in, they are greeted with a page that tells them When they try to log in, they are greeted with a page that tells them
the reason why they are banned and when (if ever) the ban will be the reason why they are banned and when (if ever) the ban will be
lifted lifted
:keyword user_id Holds the id of the user this object is :keyword user_id Holds the id of the user this object is
attached to. This is a one-to-one attached to. This is a one-to-one
relationship. relationship.
:keyword expiration_date Holds the date that the ban will be lifted. :keyword expiration_date Holds the date that the ban will be lifted.
If this is null, the ban is permanent If this is null, the ban is permanent
unless a moderator manually lifts it. unless a moderator manually lifts it.
:keyword reason Holds the reason why the user was banned. :keyword reason Holds the reason why the user was banned.
""" """
__tablename__ = 'core__user_bans' __tablename__ = 'core__user_bans'
user_id = Column(Integer, ForeignKey(User.id), nullable=False, user_id = Column(Integer, ForeignKey(User.id), nullable=False,
primary_key=True) primary_key=True)
expiration_date = Column(DateTime) expiration_date = Column(DateTime)
reason = Column(UnicodeText, nullable=False) reason = Column(UnicodeText, nullable=False)
@ -785,21 +785,21 @@ class Privilege(Base):
""" """
The Privilege table holds all of the different privileges a user can hold. The Privilege table holds all of the different privileges a user can hold.
If a user 'has' a privilege, the User object is in a relationship with the If a user 'has' a privilege, the User object is in a relationship with the
privilege object. privilege object.
:keyword privilege_name Holds a unicode object that is the recognizable :keyword privilege_name Holds a unicode object that is the recognizable
name of this privilege. This is the column name of this privilege. This is the column
used for identifying whether or not a user used for identifying whether or not a user
has a necessary privilege or not. has a necessary privilege or not.
""" """
__tablename__ = 'core__privileges' __tablename__ = 'core__privileges'
id = Column(Integer, nullable=False, primary_key=True) id = Column(Integer, nullable=False, primary_key=True)
privilege_name = Column(Unicode, nullable=False, unique=True) privilege_name = Column(Unicode, nullable=False, unique=True)
all_users = relationship( all_users = relationship(
User, User,
backref='all_privileges', backref='all_privileges',
secondary="core__privileges_users") secondary="core__privileges_users")
def __init__(self, privilege_name): def __init__(self, privilege_name):
@ -818,25 +818,25 @@ class PrivilegeUserAssociation(Base):
''' '''
This table holds the many-to-many relationship between User and Privilege This table holds the many-to-many relationship between User and Privilege
''' '''
__tablename__ = 'core__privileges_users' __tablename__ = 'core__privileges_users'
privilege_id = Column( privilege_id = Column(
'core__privilege_id', 'core__privilege_id',
Integer, Integer,
ForeignKey(User.id), ForeignKey(User.id),
primary_key=True) primary_key=True)
user_id = Column( user_id = Column(
'core__user_id', 'core__user_id',
Integer, Integer,
ForeignKey(Privilege.id), ForeignKey(Privilege.id),
primary_key=True) primary_key=True)
MODELS = [ MODELS = [
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData,
Notification, CommentNotification, ProcessingNotification, Client, Notification, CommentNotification, ProcessingNotification, Client,
CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan,
Privilege, PrivilegeUserAssociation, ArchivedReport, Privilege, PrivilegeUserAssociation, ArchivedReport,
RequestToken, AccessToken, NonceTimestamp] RequestToken, AccessToken, NonceTimestamp]
@ -854,10 +854,10 @@ MODELS = [
FOUNDATIONS = {User:user_foundations} FOUNDATIONS = {User:user_foundations}
""" """
privilege_foundations = [{'privilege_name':u'admin'}, privilege_foundations = [{'privilege_name':u'admin'},
{'privilege_name':u'moderator'}, {'privilege_name':u'moderator'},
{'privilege_name':u'uploader'}, {'privilege_name':u'uploader'},
{'privilege_name':u'reporter'}, {'privilege_name':u'reporter'},
{'privilege_name':u'commenter'}, {'privilege_name':u'commenter'},
{'privilege_name':u'active'}] {'privilege_name':u'active'}]
FOUNDATIONS = {Privilege:privilege_foundations} FOUNDATIONS = {Privilege:privilege_foundations}

View File

@ -68,24 +68,6 @@ def check_collection_slug_used(creator_id, slug, ignore_c_id):
does_exist = Session.query(Collection.id).filter(filt).first() is not None does_exist = Session.query(Collection.id).filter(filt).first() is not None
return does_exist return does_exist
def user_privileges_to_dictionary(user_id):
"""
This function accepts a users id and returns a dictionary of True or False
values for each privilege the user does or does not have. This allows for
easier referencing of a user's privileges inside templates.
"""
privilege_dictionary = {}
user = User.query.get(user_id)
users_privileges = [p_item.privilege_name for p_item in user.all_privileges]
#TODO update this to account for plugins that may add foundations
for privilege in FOUNDATIONS[Privilege]:
privilege_name = privilege['privilege_name']
if privilege_name in users_privileges:
privilege_dictionary[privilege_name]=True
else:
privilege_dictionary[privilege_name]=False
return privilege_dictionary
if __name__ == '__main__': if __name__ == '__main__':
from mediagoblin.db.open import setup_connection_and_db_from_config from mediagoblin.db.open import setup_connection_and_db_from_config

View File

@ -24,7 +24,7 @@ from mediagoblin import mg_globals as mgg
from mediagoblin import messages from mediagoblin import messages
from mediagoblin.db.models import (MediaEntry, User, MediaComment, from mediagoblin.db.models import (MediaEntry, User, MediaComment,
UserBan, Privilege) UserBan, Privilege)
from mediagoblin.tools.response import (redirect, render_404, from mediagoblin.tools.response import (redirect, render_404,
render_user_banned, json_response) render_user_banned, json_response)
from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.translate import pass_to_ugettext as _
@ -358,7 +358,7 @@ def oauth_required(controller):
error = "Missing required parameter." error = "Missing required parameter."
return json_response({"error": error}, status=400) return json_response({"error": error}, status=400)
request_validator = GMGRequestValidator() request_validator = GMGRequestValidator()
resource_endpoint = ResourceEndpoint(request_validator) resource_endpoint = ResourceEndpoint(request_validator)
valid, request = resource_endpoint.validate_protected_resource_request( valid, request = resource_endpoint.validate_protected_resource_request(

View File

@ -55,7 +55,7 @@ def adduser(args):
entry.pw_hash = auth.gen_password_hash(args.password) entry.pw_hash = auth.gen_password_hash(args.password)
entry.status = u'active' entry.status = u'active'
entry.email_verified = True entry.email_verified = True
default_privileges = [ default_privileges = [
db.Privilege.query.filter( db.Privilege.query.filter(
db.Privilege.privilege_name==u'commenter').one(), db.Privilege.privilege_name==u'commenter').one(),
db.Privilege.query.filter( db.Privilege.query.filter(

View File

@ -28,7 +28,7 @@ class MultiCheckboxField(wtforms.SelectMultipleField):
Iterating the field will produce subfields, allowing custom rendering of Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields. the enclosed checkbox fields.
code from http://wtforms.simplecodes.com/docs/1.0.4/specific_problems.html code from http://wtforms.simplecodes.com/docs/1.0.4/specific_problems.html
""" """
widget = wtforms.widgets.ListWidget(prefix_label=False) widget = wtforms.widgets.ListWidget(prefix_label=False)
@ -40,7 +40,7 @@ class PrivilegeAddRemoveForm(wtforms.Form):
class ReportResolutionForm(wtforms.Form): class ReportResolutionForm(wtforms.Form):
action_to_resolve = MultiCheckboxField( action_to_resolve = MultiCheckboxField(
_(u'What action will you take to resolve the report?'), _(u'What action will you take to resolve the report?'),
validators=[wtforms.validators.optional()], validators=[wtforms.validators.optional()],
choices=ACTION_CHOICES) choices=ACTION_CHOICES)
targeted_user = wtforms.HiddenField('', targeted_user = wtforms.HiddenField('',

View File

@ -31,17 +31,15 @@ def take_punitive_actions(request, form, report, user):
# punitive actions that a moderator could take. # punitive actions that a moderator could take.
if u'takeaway' in form.action_to_resolve.data: if u'takeaway' in form.action_to_resolve.data:
for privilege_name in form.take_away_privileges.data: for privilege_name in form.take_away_privileges.data:
privilege = Privilege.query.filter( take_away_privileges(user.username, privilege_name)
Privilege.privilege_name==privilege_name).one()
form.resolution_content.data += \ form.resolution_content.data += \
u"<br>%s took away %s\'s %s privileges" % ( u"<br>%s took away %s\'s %s privileges." % (
request.user.username, request.user.username,
user.username, user.username,
privilege.privilege_name) privilege_name)
user.all_privileges.remove(privilege)
# If the moderator elects to ban the user, a new instance of user_ban # If the moderator elects to ban the user, a new instance of user_ban
# will be created. # will be created.
if u'userban' in form.action_to_resolve.data: if u'userban' in form.action_to_resolve.data:
reason = form.resolution_content.data + \ reason = form.resolution_content.data + \
"<br>"+request.user.username "<br>"+request.user.username
@ -51,7 +49,7 @@ def take_punitive_actions(request, form, report, user):
reason= form.why_user_was_banned.data reason= form.why_user_was_banned.data
) )
Session.add(user_ban) Session.add(user_ban)
if form.user_banned_until.data is not None: if form.user_banned_until.data is not None:
form.resolution_content.data += \ form.resolution_content.data += \
u"<br>%s banned user %s until %s." % ( u"<br>%s banned user %s until %s." % (
@ -86,17 +84,17 @@ def take_punitive_actions(request, form, report, user):
deleted_comment = report.comment deleted_comment = report.comment
Session.delete(deleted_comment) Session.delete(deleted_comment)
form.resolution_content.data += \ form.resolution_content.data += \
u"<br>%s deleted the comment" % ( u"<br>%s deleted the comment." % (
request.user.username) request.user.username)
elif u'delete' in form.action_to_resolve.data and \ elif u'delete' in form.action_to_resolve.data and \
report.is_media_entry_report(): report.is_media_entry_report():
deleted_media = report.media_entry deleted_media = report.media_entry
Session.delete(deleted_media) Session.delete(deleted_media)
form.resolution_content.data += \ form.resolution_content.data += \
u"<br>%s deleted the media entry" % ( u"<br>%s deleted the media entry." % (
request.user.username) request.user.username)
# If the moderator didn't delete the content we then attach the # If the moderator didn't delete the content we then attach the
# content to the archived report. We also have to actively delete the # content to the archived report. We also have to actively delete the
# old report, since it won't be deleted by cascading. # old report, since it won't be deleted by cascading.
elif report.is_comment_report(): elif report.is_comment_report():
@ -133,3 +131,34 @@ def take_punitive_actions(request, form, report, user):
request, request,
'mediagoblin.moderation.reports_detail', 'mediagoblin.moderation.reports_detail',
report_id=report.id) report_id=report.id)
def take_away_privileges(user,*privileges):
if len(privileges) == 1:
privilege = Privilege.query.filter(
Privilege.privilege_name==privileges[0]).first()
user = User.query.filter(
User.username==user).first()
if privilege in user.all_privileges:
user.all_privileges.remove(privilege)
return True
return False
elif len(privileges) > 1:
return (take_away_privileges(user, privileges[0]) and \
take_away_privileges(user, *privileges[1:]))
def give_privileges(user,*privileges):
if len(privileges) == 1:
privilege = Privilege.query.filter(
Privilege.privilege_name==privileges[0]).first()
user = User.query.filter(
User.username==user).first()
if privilege not in user.all_privileges:
user.all_privileges.append(privilege)
return True
return False
elif len(privileges) > 1:
return (give_privileges(user, privileges[0]) and \
give_privileges(user, *privileges[1:]))

View File

@ -19,12 +19,12 @@ from werkzeug.exceptions import Forbidden
from mediagoblin.db.models import (MediaEntry, User, MediaComment, \ from mediagoblin.db.models import (MediaEntry, User, MediaComment, \
CommentReport, ReportBase, Privilege, \ CommentReport, ReportBase, Privilege, \
UserBan, ArchivedReport) UserBan, ArchivedReport)
from mediagoblin.db.util import user_privileges_to_dictionary
from mediagoblin.decorators import (require_admin_or_moderator_login, \ from mediagoblin.decorators import (require_admin_or_moderator_login, \
active_user_from_url) active_user_from_url, user_has_privilege)
from mediagoblin.tools.response import render_to_response, redirect from mediagoblin.tools.response import render_to_response, redirect
from mediagoblin.moderation import forms as moderation_forms from mediagoblin.moderation import forms as moderation_forms
from mediagoblin.moderation.tools import take_punitive_actions from mediagoblin.moderation.tools import (take_punitive_actions, \
take_away_privileges, give_privileges)
from datetime import datetime from datetime import datetime
@require_admin_or_moderator_login @require_admin_or_moderator_login
@ -86,7 +86,7 @@ def moderation_users_detail(request):
@require_admin_or_moderator_login @require_admin_or_moderator_login
def moderation_reports_panel(request): def moderation_reports_panel(request):
''' '''
Show the global panel for monitoring reports filed against comments or Show the global panel for monitoring reports filed against comments or
media entries for this instance. media entries for this instance.
''' '''
report_list = ReportBase.query.filter( report_list = ReportBase.query.filter(
@ -115,7 +115,7 @@ def moderation_reports_detail(request):
form.take_away_privileges.choices = [ form.take_away_privileges.choices = [
(s.privilege_name,s.privilege_name.title()) \ (s.privilege_name,s.privilege_name.title()) \
for s in report.reported_user.all_privileges for s in report.reported_user.all_privileges
] ]
if request.method == "POST" and form.validate() and not ( if request.method == "POST" and form.validate() and not (
@ -134,7 +134,7 @@ def moderation_reports_detail(request):
{'report':report, {'report':report,
'form':form}) 'form':form})
@require_admin_or_moderator_login @user_has_privilege(u'admin')
@active_user_from_url @active_user_from_url
def give_or_take_away_privilege(request, url_user): def give_or_take_away_privilege(request, url_user):
''' '''
@ -144,12 +144,13 @@ def give_or_take_away_privilege(request, url_user):
if request.method == "POST" and form.validate(): if request.method == "POST" and form.validate():
privilege = Privilege.query.filter( privilege = Privilege.query.filter(
Privilege.privilege_name==form.privilege_name.data).one() Privilege.privilege_name==form.privilege_name.data).one()
if privilege in url_user.all_privileges: if not take_away_privileges(
url_user.all_privileges.remove(privilege) url_user.username, form.privilege_name.data):
else:
url_user.all_privileges.append(privilege) give_privileges(url_user.username, form.privilege_name.data)
url_user.save() url_user.save()
return redirect(
request, return redirect(
'mediagoblin.moderation.users_detail', request,
user=url_user.username) 'mediagoblin.moderation.users_detail',
user=url_user.username)

View File

@ -17,12 +17,19 @@
#} #}
{% extends "mediagoblin/base.html" %} {% extends "mediagoblin/base.html" %}
{% block title %}You are Banned.{% endblock %} {% block title %}{% trans %}You are Banned.{% endtrans %}{% endblock %}
{% block mediagoblin_content %} {% block mediagoblin_content %}
<img class="right_align" src="{{ request.staticdirect('/images/404.png') }}" <img class="right_align" src="{{ request.staticdirect('/images/404.png') }}"
alt="{% trans %}Image of goblin stressing out{% endtrans %}" /> alt="{% trans %}Image of goblin stressing out{% endtrans %}" />
<h1>You have been banned until {{ expiration_date }}</h1>
<h1>{% trans %}You have been banned{% endtrans %}
{% if expiration_date %}
{% trans %}until{% endtrans %} {{ expiration_date }}
{% else %}
{% trans %}indefinitely{% endtrans %}
{% endif %}
</h2>
<p>{{ reason|safe }}</p> <p>{{ reason|safe }}</p>
<div class="clear"></div> <div class="clear"></div>
{% endblock %} {% endblock %}

View File

@ -28,7 +28,7 @@
<p> <p>
{% trans %}Here you can track the state of media being processed on this instance.{% endtrans %} {% trans %}Here you can track the state of media being processed on this instance.{% endtrans %}
</p> </p>
<h2>{% trans %}Media in-processing{% endtrans %}</h2> <h2>{% trans %}Media in-processing{% endtrans %}</h2>
{% if processing_entries.count() %} {% if processing_entries.count() %}
@ -56,7 +56,7 @@
</table> </table>
{% else %} {% else %}
<p><em>{% trans %}No media in-processing{% endtrans %}</em></p> <p><em>{% trans %}No media in-processing{% endtrans %}</em></p>
{% endif %} {% endif %}
<h2>{% trans %}These uploads failed to process:{% endtrans %}</h2> <h2>{% trans %}These uploads failed to process:{% endtrans %}</h2>
{% if failed_entries.count() %} {% if failed_entries.count() %}

View File

@ -27,7 +27,7 @@
title="Return to Reports Panel"> title="Return to Reports Panel">
{% trans %}Return to Reports Panel{% endtrans %}</a> {% trans %}Return to Reports Panel{% endtrans %}</a>
<h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2> <h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2>
{% if report.is_comment_report() or {% if report.is_comment_report() or
(report.is_archived_report() and report.comment) %} (report.is_archived_report() and report.comment) %}
{% trans %}Reported comment{% endtrans %}: {% trans %}Reported comment{% endtrans %}:
@ -60,7 +60,7 @@
{% endautoescape %} {% endautoescape %}
</div> </div>
</div> </div>
{% elif report.is_media_entry_report() or {% elif report.is_media_entry_report() or
(report.is_archived_report() and report.media_entry) %} (report.is_archived_report() and report.media_entry) %}
{% set media_entry = report.media_entry %} {% set media_entry = report.media_entry %}
@ -89,7 +89,7 @@
'mediagoblin.moderation.users_detail', 'mediagoblin.moderation.users_detail',
user=report.reporter.username), user=report.reporter.username),
user_name=report.reported_user.username %} user_name=report.reported_user.username %}
CONTENT BY CONTENT BY
<a href="{{ user_url }}"> {{ user_name }}</a> <a href="{{ user_url }}"> {{ user_name }}</a>
HAS BEEN DELETED HAS BEEN DELETED
{% endtrans %} {% endtrans %}
@ -100,8 +100,8 @@
class="report_wrapper"> class="report_wrapper">
<div class="report_author"> <div class="report_author">
<img src="{{ request.staticdirect( <img src="{{ request.staticdirect(
'/images/icon_clipboard_alert.png') }}" '/images/icon_clipboard_alert.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" /> Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen('mediagoblin.moderation.users_detail', <a href="{{ request.urlgen('mediagoblin.moderation.users_detail',
user=report.reporter.username) }}" user=report.reporter.username) }}"
@ -160,7 +160,7 @@ $(document).ready(function() {
$('ul#action_to_resolve li input:not(:checked)').each(function() { $('ul#action_to_resolve li input:not(:checked)').each(function() {
$.each(hidden_input_names[$(this).val()], function(index, name){ $.each(hidden_input_names[$(this).val()], function(index, name){
$('label[for='+name+']').hide(); $('label[for='+name+']').hide();
$('#'+name).hide(); $('#'+name).hide();
}); });
}); });
}); });
@ -176,12 +176,12 @@ $(document).ready(function() {
}); });
</script> </script>
{% elif not (report.reported_user.has_privilege('admin')) %} {% elif not (report.reported_user.has_privilege('admin')) %}
<h2><img src="{{ request.staticdirect('/images/icon_clipboard.png') }}" <h2><img src="{{ request.staticdirect('/images/icon_clipboard.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" /> Distributed by the GNOME project http://www.gnome.org" />
{% trans %}Status{% endtrans %}: {% trans %}Status{% endtrans %}:
</h2> </h2>
<b>{% trans %}RESOLVED{% endtrans %}</b> <b>{% trans %}RESOLVED{% endtrans %}</b>
{{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }} {{ report.resolved.strftime("%I:%M%p %Y-%m-%d") }}
{% autoescape False %} {% autoescape False %}
<p>{{ report.result }}</p> <p>{{ report.result }}</p>

View File

@ -30,7 +30,7 @@
Here you can look up open reports that have been filed by users. Here you can look up open reports that have been filed by users.
{% endtrans %} {% endtrans %}
</p> </p>
<h2>{% trans %}Active Reports Filed{% endtrans %}</h2> <h2>{% trans %}Active Reports Filed{% endtrans %}</h2>
{% if report_list.count() %} {% if report_list.count() %}
@ -46,22 +46,22 @@
<tr> <tr>
{% if report.discriminator == "comment_report" %} {% if report.discriminator == "comment_report" %}
<td> <td>
<img <img
src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}" src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" /> Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen( <a href="{{ request.urlgen(
'mediagoblin.moderation.reports_detail', 'mediagoblin.moderation.reports_detail',
report_id=report.id) }}"> report_id=report.id) }}">
{% trans report_id=report.id %} {% trans report_id=report.id %}
Comment Report #{{ report_id }} Comment Report #{{ report_id }}
{% endtrans %} {% endtrans %}
</a> </a>
</td> </td>
{% elif report.discriminator == "media_report" %} {% elif report.discriminator == "media_report" %}
<td> <td>
<img <img
src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}" src="{{ request.staticdirect('/images/icon_clipboard_alert.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" /> Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen( <a href="{{ request.urlgen(
@ -97,8 +97,8 @@
{% for report in closed_report_list %} {% for report in closed_report_list %}
<tr> <tr>
<td> <td>
<img <img
src="{{ request.staticdirect('/images/icon_clipboard.png') }}" src="{{ request.staticdirect('/images/icon_clipboard.png') }}"
alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license. alt="Under a GNU LGPL v.3 or Creative Commons BY-SA 3.0 license.
Distributed by the GNOME project http://www.gnome.org" /> Distributed by the GNOME project http://www.gnome.org" />
<a href="{{ request.urlgen('mediagoblin.moderation.reports_detail', <a href="{{ request.urlgen('mediagoblin.moderation.reports_detail',
@ -118,5 +118,5 @@
</table> </table>
{% else %} {% else %}
<p><em>{% trans %}No closed reports found.{% endtrans %}</em></p> <p><em>{% trans %}No closed reports found.{% endtrans %}</em></p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -130,9 +130,9 @@
<a class="right_align">{{ user.username }}'s report history</a> <a class="right_align">{{ user.username }}'s report history</a>
<span class=clear></span> <span class=clear></span>
<h2>{{ user.username }}'s Privileges</h2> <h2>{{ user.username }}'s Privileges</h2>
{% if request.user.has_privilege('admin') and not user_banned and {% if request.user.has_privilege('admin') and not user_banned and
not user.id == request.user.id %} not user.id == request.user.id %}
<input type=button class="button_action right_align" <input type=button class="button_action right_align"
value="Ban User" /> value="Ban User" />
{% elif request.user.has_privilege('admin') and {% elif request.user.has_privilege('admin') and
not user.id == request.user.id %} not user.id == request.user.id %}

View File

@ -30,7 +30,7 @@
Here you can look up users in order to take punitive actions on them. Here you can look up users in order to take punitive actions on them.
{% endtrans %} {% endtrans %}
</p> </p>
<h2>{% trans %}Active Users{% endtrans %}</h2> <h2>{% trans %}Active Users{% endtrans %}</h2>
{% if user_list.count() %} {% if user_list.count() %}
@ -57,5 +57,5 @@
</table> </table>
{% else %} {% else %}
<p><em>{% trans %}No users found.{% endtrans %}</em></p> <p><em>{% trans %}No users found.{% endtrans %}</em></p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -86,7 +86,7 @@
{% autoescape False %} {% autoescape False %}
<p>{{ media.description_html }}</p> <p>{{ media.description_html }}</p>
{% endautoescape %} {% endautoescape %}
{% if comments %} {% if comments and request.user.has_privilege('commenter') %}
{% if app_config['allow_comments'] %} {% if app_config['allow_comments'] %}
<a <a
{% if not request.user %} {% if not request.user %}

View File

@ -26,15 +26,15 @@
{%- set comment_author_url = request.urlgen( {%- set comment_author_url = request.urlgen(
'mediagoblin.user_pages.user_home', 'mediagoblin.user_pages.user_home',
user=comment_author.username) %} user=comment_author.username) %}
{%- set comment_url = request.urlgen( {%- set comment_url = request.urlgen(
'mediagoblin.user_pages.media_home.view_comment', 'mediagoblin.user_pages.media_home.view_comment',
comment=comment.id, comment=comment.id,
user=media.get_uploader.username, user=media.get_uploader.username,
media=media.slug_or_id) %} media=media.slug_or_id) %}
<div id="comment-{{ comment.id }}" <div id="comment-{{ comment.id }}"
class="comment_wrapper"> class="comment_wrapper">
<div class="comment_author"> <div class="comment_author">
<img <img
src="{{ request.staticdirect('/images/icon_comment.png') }}" /> src="{{ request.staticdirect('/images/icon_comment.png') }}" />
<a href="{{ comment_author_url }}" <a href="{{ comment_author_url }}"
class="comment_authorlink"> class="comment_authorlink">
@ -42,7 +42,7 @@
</a> </a>
<a href="{{ comment_url }}" <a href="{{ comment_url }}"
class="comment_whenlink"> class="comment_whenlink">
<span <span
title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'> title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
{%- trans formatted_time=timesince(comment.created) -%} {%- trans formatted_time=timesince(comment.created) -%}
@ -59,19 +59,19 @@
{% elif media is defined %} {% elif media is defined %}
<h3>{% trans %}Reporting this Media Entry{% endtrans %}</h3> <h3>{% trans %}Reporting this Media Entry{% endtrans %}</h3>
<div class="media_thumbnail"> <div class="media_thumbnail">
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home', <a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media.get_uploader.username, user=media.get_uploader.username,
media=media.slug_or_id) }}"> media=media.slug_or_id) }}">
<img src="{{ media.thumb_url }}"/></a> <img src="{{ media.thumb_url }}"/></a>
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home', <a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media.get_uploader.username, user=media.get_uploader.username,
media=media.slug_or_id) }}" media=media.slug_or_id) }}"
class=thumb_entry_title>{{ media.title }}</a> class=thumb_entry_title>{{ media.title }}</a>
</div> </div>
<div class=clear></div> <div class=clear></div>
{%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username), {%- trans user_url = request.urlgen('mediagoblin.user_pages.user_home', user=media.get_uploader.username),
username = media.get_uploader.username %} username = media.get_uploader.username %}
❖ Published by <a href="{{ user_url }}" ❖ Published by <a href="{{ user_url }}"
class="comment_authorlink">{{ username }}</a> class="comment_authorlink">{{ username }}</a>
{% endtrans %} {% endtrans %}
{%- endif %} {%- endif %}

View File

@ -18,7 +18,7 @@
{% block report_content -%} {% block report_content -%}
<p> <p>
<a <a
{% if not request.user -%} {% if not request.user -%}
href="{{ request.urlgen('mediagoblin.auth.login') }}" href="{{ request.urlgen('mediagoblin.auth.login') }}"
{% else %} {% else %}

View File

@ -330,6 +330,13 @@ def test_authentication_views(test_app):
'next' : '/u/chris/'}) 'next' : '/u/chris/'})
assert urlparse.urlsplit(response.location)[2] == '/u/chris/' assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
def test_basic_privileges_granted_on_registration(test_app):
user = User.query.filter(User.username==u'angrygirl').first()
assert User.has_privilege(u'commenter')
assert User.has_privilege(u'uploader')
assert User.has_privilege(u'reporter')
assert not User.has_privilege(u'active')
@pytest.fixture() @pytest.fixture()
def authentication_disabled_app(request): def authentication_disabled_app(request):

View File

@ -0,0 +1,194 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from mediagoblin.tests.tools import (fixture_add_user,
fixture_add_comment_report, fixture_add_comment)
from mediagoblin.db.models import User, CommentReport, MediaComment, UserBan
from mediagoblin.moderation.tools import take_away_privileges, give_privileges
from mediagoblin.tools import template, mail
from webtest import AppError
class TestModerationViews:
@pytest.fixture(autouse=True)
def _setup(self, test_app):
self.test_app = test_app
fixture_add_user(u'admin',
privileges=[u'admin',u'active'])
fixture_add_user(u'moderator',
privileges=[u'moderator',u'active'])
fixture_add_user(u'regular',
privileges=[u'active',u'commenter'])
self.query_for_users()
def login(self, username):
self.test_app.post(
'/auth/login/', {
'username': username,
'password': 'toast'})
self.query_for_users()
def logout(self):
self.test_app.get('/auth/logout/')
self.query_for_users()
def query_for_users(self):
self.admin_user = User.query.filter(User.username==u'admin').first()
self.mod_user = User.query.filter(User.username==u'moderator').first()
self.user = User.query.filter(User.username==u'regular').first()
def do_post(self, data, *context_keys, **kwargs):
url = kwargs.pop('url', '/submit/')
do_follow = kwargs.pop('do_follow', False)
template.clear_test_template_context()
response = self.test_app.post(url, data, **kwargs)
if do_follow:
response.follow()
context_data = template.TEMPLATE_TEST_CONTEXT
for key in context_keys:
context_data = context_data[key]
return response, context_data
def testGiveOrTakeAwayPrivileges(self):
self.login(u'admin')
# First, test an admin taking away a privilege from a user
#----------------------------------------------------------------------
response, context = self.do_post({'privilege_name':u'commenter'},
url='/mod/users/{0}/privilege/'.format(self.user.username))
assert response.status == '302 FOUND'
self.query_for_users()
assert not self.user.has_privilege(u'commenter')
# Then, test an admin giving a privilege to a user
#----------------------------------------------------------------------
response, context = self.do_post({'privilege_name':u'commenter'},
url='/mod/users/{0}/privilege/'.format(self.user.username))
assert response.status == '302 FOUND'
self.query_for_users()
assert self.user.has_privilege(u'commenter')
# Then, test a mod trying to take away a privilege from a user
# they are not allowed to do this, so this will raise an error
#----------------------------------------------------------------------
self.logout()
self.login(u'moderator')
with pytest.raises(AppError) as excinfo:
response, context = self.do_post({'privilege_name':u'commenter'},
url='/mod/users/{0}/privilege/'.format(self.user.username))
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
self.query_for_users()
assert self.user.has_privilege(u'commenter')
def testReportResolution(self):
self.login(u'moderator')
# First, test a moderators taking away a user's privilege in response
# to a reported comment
#----------------------------------------------------------------------
fixture_add_comment_report(reported_user=self.user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
response = self.test_app.get('/mod/reports/{0}/'.format(
comment_report.id))
assert response.status == '200 OK'
self.query_for_users()
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'commenter'],
'targeted_user':self.user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
assert response.status == '302 FOUND'
fixture_add_comment_report(reported_user=self.user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
assert not self.user.has_privilege(u'commenter')
# Then, test a moderator sending an email to a user in response to a
# reported comment
#----------------------------------------------------------------------
self.query_for_users()
response, context = self.do_post({'action_to_resolve':[u'sendmessage'],
'message_to_user':'This is your last warning, regular....',
'targeted_user':self.user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
assert response.status == '302 FOUND'
assert mail.EMAIL_TEST_MBOX_INBOX == [{'to': [u'regular@example.com'],
'message': 'Content-Type: text/plain; charset="utf-8"\n\
MIME-Version: 1.0\nContent-Transfer-Encoding: base64\nSubject: Warning from- \
moderator \nFrom: notice@mediagoblin.example.org\nTo: regular@example.com\n\n\
VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
'from': 'notice@mediagoblin.example.org'}]
# Then test a moderator banning a user AND a moderator deleting the
# offending comment. This also serves as a test for taking multiple
# actions to resolve a report
#----------------------------------------------------------------------
self.query_for_users()
fixture_add_comment(author=self.user.id,
comment=u'Comment will be removed')
test_comment = MediaComment.query.filter(
MediaComment.author==self.user.id).first()
fixture_add_comment_report(comment=test_comment,
reported_user=self.user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.user).first()
response, context = self.do_post(
{'action_to_resolve':[u'userban', u'delete'],
'targeted_user':self.user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
assert response.status == '302 FOUND'
self.query_for_users()
test_user_ban = UserBan.query.filter(
UserBan.user_id == self.user.id).first()
assert test_user_ban is not None
test_comment = MediaComment.query.filter(
MediaComment.author==self.user.id).first()
assert test_comment is None
# Then, test what happens when a moderator attempts to punish an admin
# from a reported comment on an admin.
#----------------------------------------------------------------------
fixture_add_comment_report(reported_user=self.admin_user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==self.admin_user).first()
response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'active'],
'targeted_user':self.admin_user.id},
url='/mod/reports/{0}/'.format(comment_report.id))
self.query_for_users()
assert response.status == '200 OK'
assert self.admin_user.has_privilege(u'active')
def testAllModerationViews(self):
self.login(u'moderator')
self.test_app.get('/mod/reports/')
self.test_app.get('/mod/users/')
self.test_app.get('/mod/media/')

View File

@ -127,7 +127,6 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
else: else:
assert mail.EMAIL_TEST_MBOX_INBOX == [] assert mail.EMAIL_TEST_MBOX_INBOX == []
mail.EMAIL_TEST_MBOX_INBOX = []
# Save the ids temporarily because of DetachedInstanceError # Save the ids temporarily because of DetachedInstanceError
notification_id = notification.id notification_id = notification.id

View File

@ -0,0 +1,206 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from datetime import datetime, timedelta
from webtest import AppError
from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
from mediagoblin.db.models import User, Privilege, UserBan
from mediagoblin.db.base import Session
from mediagoblin.tools import template
from .resources import GOOD_JPG
class TestPrivilegeFunctionality:
@pytest.fixture(autouse=True)
def _setup(self, test_app):
self.test_app = test_app
fixture_add_user(u'alex',
privileges=[u'admin',u'active'])
fixture_add_user(u'raven',
privileges=[u'moderator',u'active',u'reporter'])
fixture_add_user(u'natalie',
privileges=[u'active'])
self.query_for_users()
def login(self, username):
self.test_app.post(
'/auth/login/', {
'username': username,
'password': 'toast'})
self.query_for_users()
def logout(self):
self.test_app.get('/auth/logout/')
self.query_for_users()
def do_post(self, data, *context_keys, **kwargs):
url = kwargs.pop('url', '/submit/')
do_follow = kwargs.pop('do_follow', False)
template.clear_test_template_context()
response = self.test_app.post(url, data, **kwargs)
if do_follow:
response.follow()
context_data = template.TEMPLATE_TEST_CONTEXT
for key in context_keys:
context_data = context_data[key]
return response, context_data
def query_for_users(self):
self.admin_user = User.query.filter(User.username==u'alex').first()
self.mod_user = User.query.filter(User.username==u'raven').first()
self.user = User.query.filter(User.username==u'natalie').first()
def testUserBanned(self):
self.login(u'natalie')
uid = self.user.id
# First, test what happens when a user is banned indefinitely
#----------------------------------------------------------------------
user_ban = UserBan(user_id=uid,
reason=u'Testing whether user is banned',
expiration_date=None)
user_ban.save()
response = self.test_app.get('/')
assert response.status == "200 OK"
assert "You are Banned" in response.body
# Then test what happens when that ban has an expiration date which
# hasn't happened yet
#----------------------------------------------------------------------
user_ban = UserBan.query.get(uid)
user_ban.delete()
user_ban = UserBan(user_id=uid,
reason=u'Testing whether user is banned',
expiration_date= datetime.now() + timedelta(days=20))
user_ban.save()
response = self.test_app.get('/')
assert response.status == "200 OK"
assert "You are Banned" in response.body
# Then test what happens when that ban has an expiration date which
# has already happened
#----------------------------------------------------------------------
user_ban = UserBan.query.get(uid)
user_ban.delete()
exp_date = datetime.now() - timedelta(days=20)
user_ban = UserBan(user_id=uid,
reason=u'Testing whether user is banned',
expiration_date= exp_date)
user_ban.save()
response = self.test_app.get('/')
assert response.status == "302 FOUND"
assert not "You are Banned" in response.body
def testVariousPrivileges(self):
# The various actions that require privileges (ex. reporting,
# commenting, moderating...) are tested in other tests. This method
# will be used to ensure that those actions are impossible for someone
# without the proper privileges.
# For other tests that show what happens when a user has the proper
# privileges, check out:
# tests/test_moderation.py moderator
# tests/test_notifications.py commenter
# tests/test_reporting.py reporter
# tests/test_submission.py uploader
#----------------------------------------------------------------------
self.login(u'natalie')
# First test the get and post requests of submission/uploading
#----------------------------------------------------------------------
with pytest.raises(AppError) as excinfo:
response = self.test_app.get('/submit/')
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.do_post({'upload_files':[('file',GOOD_JPG)],
'title':u'Normal Upload 1'},
url='/submit/')
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
# Test that a user cannot comment without the commenter privilege
#----------------------------------------------------------------------
self.query_for_users()
media_entry = fixture_media_entry(uploader=self.admin_user.id,
state=u'processed')
media_entry_id = media_entry.id
media_uri_id = '/u/{0}/m/{1}/'.format(self.admin_user.username,
media_entry.id)
media_uri_slug = '/u/{0}/m/{1}/'.format(self.admin_user.username,
media_entry.slug)
response = self.test_app.get(media_uri_slug)
assert not "Add a comment" in response.body
self.query_for_users()
with pytest.raises(AppError) as excinfo:
response = self.test_app.post(
media_uri_id + 'comment/add/',
{'comment_content': u'Test comment #42'})
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
# Test that a user cannot report without the reporter privilege
#----------------------------------------------------------------------
with pytest.raises(AppError) as excinfo:
response = self.test_app.get(media_uri_slug+"report/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.do_post(
{'report_reason':u'Testing Reports #1',
'reporter_id':u'3'},
url=(media_uri_slug+"report/"))
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
# Test that a user cannot access the moderation pages w/o moderator
# or admin privileges
#----------------------------------------------------------------------
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/users/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/reports/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/media/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/users/1/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
with pytest.raises(AppError) as excinfo:
response = self.test_app.get("/mod/reports/1/")
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
self.query_for_users()
with pytest.raises(AppError) as excinfo:
response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'active'],
'targeted_user':self.admin_user.id},
url='/mod/reports/1/')
self.query_for_users()
assert 'Bad response: 403 FORBIDDEN' in str(excinfo)

View File

@ -0,0 +1,165 @@
# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from mediagoblin.tools import template
from mediagoblin.tests.tools import (fixture_add_user, fixture_media_entry,
fixture_add_comment, fixture_add_comment_report)
from mediagoblin.db.models import (MediaReport, CommentReport, User,
MediaComment,ArchivedReport)
class TestReportFiling:
@pytest.fixture(autouse=True)
def _setup(self, test_app):
self.test_app = test_app
fixture_add_user(u'allie',
privileges=[u'reporter',u'active'])
fixture_add_user(u'natalie',
privileges=[u'active', u'moderator'])
def login(self, username):
self.test_app.post(
'/auth/login/', {
'username': username,
'password': 'toast'})
def logout(self):
self.test_app.get('/auth/logout/')
def do_post(self, data, *context_keys, **kwargs):
url = kwargs.pop('url', '/submit/')
do_follow = kwargs.pop('do_follow', False)
template.clear_test_template_context()
response = self.test_app.post(url, data, **kwargs)
if do_follow:
response.follow()
context_data = template.TEMPLATE_TEST_CONTEXT
for key in context_keys:
context_data = context_data[key]
return response, context_data
def query_for_users(self):
return (User.query.filter(User.username==u'allie').first(),
User.query.filter(User.username==u'natalie').first())
def testMediaReports(self):
self.login(u'allie')
allie_user, natalie_user = self.query_for_users()
allie_id = allie_user.id
media_entry = fixture_media_entry(uploader=natalie_user.id,
state=u'processed')
mid = media_entry.id
media_uri_slug = '/u/{0}/m/{1}/'.format(natalie_user.username,
media_entry.slug)
response = self.test_app.get(media_uri_slug + "report/")
assert response.status == "200 OK"
response, context = self.do_post(
{'report_reason':u'Testing Media Report',
'reporter_id':unicode(allie_id)},url= media_uri_slug + "report/")
assert response.status == "302 FOUND"
media_report = MediaReport.query.first()
allie_user, natalie_user = self.query_for_users()
assert media_report is not None
assert media_report.report_content == u'Testing Media Report'
assert media_report.reporter_id == allie_id
assert media_report.reported_user_id == natalie_user.id
assert media_report.created is not None
assert media_report.discriminator == 'media_report'
def testCommentReports(self):
self.login(u'allie')
allie_user, natalie_user = self.query_for_users()
allie_id = allie_user.id
media_entry = fixture_media_entry(uploader=natalie_user.id,
state=u'processed')
mid = media_entry.id
fixture_add_comment(media_entry=mid,
author=natalie_user.id)
comment = MediaComment.query.first()
comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username,
media_entry.slug,
comment.id)
response = self.test_app.get(comment_uri_slug + "report/")
assert response.status == "200 OK"
response, context = self.do_post({
'report_reason':u'Testing Comment Report',
'reporter_id':unicode(allie_id)},url= comment_uri_slug + "report/")
assert response.status == "302 FOUND"
comment_report = CommentReport.query.first()
allie_user, natalie_user = self.query_for_users()
assert comment_report is not None
assert comment_report.report_content == u'Testing Comment Report'
assert comment_report.reporter_id == allie_id
assert comment_report.reported_user_id == natalie_user.id
assert comment_report.created is not None
assert comment_report.discriminator == 'comment_report'
def testArchivingReports(self):
self.login(u'natalie')
allie_user, natalie_user = self.query_for_users()
allie_id, natalie_id = allie_user.id, natalie_user.id
fixture_add_comment(author=allie_user.id,
comment=u'Comment will be removed')
test_comment = MediaComment.query.filter(
MediaComment.author==allie_user.id).first()
fixture_add_comment_report(comment=test_comment,
reported_user=allie_user,
report_content=u'Testing Archived Reports #1',
reporter=natalie_user)
comment_report = CommentReport.query.filter(
CommentReport.reported_user==allie_user).first()
assert comment_report.report_content == u'Testing Archived Reports #1'
response, context = self.do_post(
{'action_to_resolve':[u'userban', u'delete'],
'targeted_user':allie_user.id,
'resolution_content':u'This is a test of archiving reports.'},
url='/mod/reports/{0}/'.format(comment_report.id))
assert response.status == "302 FOUND"
self.query_for_users()
archived_report = ArchivedReport.query.first()
assert CommentReport.query.count() == 0
assert archived_report is not None
assert archived_report.report_content == u'Testing Archived Reports #1'
assert archived_report.reporter_id == natalie_id
assert archived_report.reported_user_id == allie_id
assert archived_report.created is not None
assert archived_report.resolved is not None
assert archived_report.result == u'This is a test of archiving reports\
.<br>natalie banned user allie indefinitely.<br>natalie deleted the comment.'
assert archived_report.discriminator == 'archived_report'

View File

@ -24,7 +24,7 @@ import pytest
from mediagoblin.tests.tools import fixture_add_user from mediagoblin.tests.tools import fixture_add_user
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.models import MediaEntry, User from mediagoblin.db.models import MediaEntry, User, Privilege
from mediagoblin.tools import template from mediagoblin.tools import template
from mediagoblin.media_types.image import ImageMediaManager from mediagoblin.media_types.image import ImageMediaManager
from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites
@ -48,10 +48,20 @@ class TestSubmission:
# @as_authenticated_user('chris') # @as_authenticated_user('chris')
fixture_add_user(privileges=[u'active',u'uploader']) fixture_add_user(privileges=[u'active',u'uploader'])
self.test_user = User.query.filter(User.username==u'chris').first()
self.login() self.login()
def our_user(self):
"""
Fetch the user we're submitting with. Every .get() or .post()
invalidates the session; this is a hacky workaround.
"""
#### FIXME: Pytest collects this as a test and runs this.
#### ... it shouldn't. At least it passes, but that's
#### totally stupid.
#### Also if we found a way to make this run it should be a
#### property.
return User.query.filter(User.username==u'chris').first()
def login(self): def login(self):
self.test_app.post( self.test_app.post(
'/auth/login/', { '/auth/login/', {
@ -97,10 +107,10 @@ class TestSubmission:
def check_normal_upload(self, title, filename): def check_normal_upload(self, title, filename):
response, context = self.do_post({'title': title}, do_follow=True, response, context = self.do_post({'title': title}, do_follow=True,
**self.upload_data(filename)) **self.upload_data(filename))
self.check_url(response, '/u/{0}/'.format(self.test_user.username)) self.check_url(response, '/u/{0}/'.format(self.our_user().username))
assert 'mediagoblin/user_pages/user.html' in context assert 'mediagoblin/user_pages/user.html' in context
# Make sure the media view is at least reachable, logged in... # Make sure the media view is at least reachable, logged in...
url = '/u/{0}/m/{1}/'.format(self.test_user.username, url = '/u/{0}/m/{1}/'.format(self.our_user().username,
title.lower().replace(' ', '-')) title.lower().replace(' ', '-'))
self.test_app.get(url) self.test_app.get(url)
# ... and logged out too. # ... and logged out too.
@ -118,7 +128,7 @@ class TestSubmission:
response, context = self.do_post({'title': u'Normal upload 3 (pdf)'}, response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
do_follow=True, do_follow=True,
**self.upload_data(GOOD_PDF)) **self.upload_data(GOOD_PDF))
self.check_url(response, '/u/{0}/'.format(self.test_user.username)) self.check_url(response, '/u/{0}/'.format(self.our_user().username))
assert 'mediagoblin/user_pages/user.html' in context assert 'mediagoblin/user_pages/user.html' in context
def check_media(self, request, find_data, count=None): def check_media(self, request, find_data, count=None):
@ -164,7 +174,7 @@ class TestSubmission:
# render and post to the edit page. # render and post to the edit page.
edit_url = request.urlgen( edit_url = request.urlgen(
'mediagoblin.edit.edit_media', 'mediagoblin.edit.edit_media',
user=self.test_user.username, media_id=media_id) user=self.our_user().username, media_id=media_id)
self.test_app.get(edit_url) self.test_app.get(edit_url)
self.test_app.post(edit_url, self.test_app.post(edit_url,
{'title': u'Balanced Goblin', {'title': u'Balanced Goblin',
@ -177,7 +187,7 @@ class TestSubmission:
self.check_comments(request, media_id, 0) self.check_comments(request, media_id, 0)
comment_url = request.urlgen( comment_url = request.urlgen(
'mediagoblin.user_pages.media_post_comment', 'mediagoblin.user_pages.media_post_comment',
user=self.test_user.username, media_id=media_id) user=self.our_user().username, media_id=media_id)
response = self.do_post({'comment_content': 'i love this test'}, response = self.do_post({'comment_content': 'i love this test'},
url=comment_url, do_follow=True)[0] url=comment_url, do_follow=True)[0]
self.check_comments(request, media_id, 1) self.check_comments(request, media_id, 1)
@ -186,7 +196,7 @@ class TestSubmission:
# --------------------------------------------------- # ---------------------------------------------------
delete_url = request.urlgen( delete_url = request.urlgen(
'mediagoblin.user_pages.media_confirm_delete', 'mediagoblin.user_pages.media_confirm_delete',
user=self.test_user.username, media_id=media_id) user=self.our_user().username, media_id=media_id)
# Empty data means don't confirm # Empty data means don't confirm
response = self.do_post({}, do_follow=True, url=delete_url)[0] response = self.do_post({}, do_follow=True, url=delete_url)[0]
media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
@ -251,7 +261,7 @@ class TestSubmission:
# they'll be caught as failures during the processing step. # they'll be caught as failures during the processing step.
response, context = self.do_post({'title': title}, do_follow=True, response, context = self.do_post({'title': title}, do_follow=True,
**self.upload_data(filename)) **self.upload_data(filename))
self.check_url(response, '/u/{0}/'.format(self.test_user.username)) self.check_url(response, '/u/{0}/'.format(self.our_user().username))
entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first() entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
assert entry.state == 'failed' assert entry.state == 'failed'
assert entry.fail_error == u'mediagoblin.processing:BadMediaFail' assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'

View File

@ -25,7 +25,7 @@ from webtest import TestApp
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \ from mediagoblin.db.models import User, MediaEntry, Collection, MediaComment, \
CommentSubscription, CommentNotification, Privilege CommentSubscription, CommentNotification, Privilege, CommentReport
from mediagoblin.tools import testing from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.base import Session from mediagoblin.db.base import Session
@ -33,6 +33,8 @@ from mediagoblin.meddleware import BaseMeddleware
from mediagoblin.auth import gen_password_hash from mediagoblin.auth import gen_password_hash
from mediagoblin.gmg_commands.dbupdate import run_dbupdate from mediagoblin.gmg_commands.dbupdate import run_dbupdate
from datetime import datetime
MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__'
TEST_SERVER_CONFIG = pkg_resources.resource_filename( TEST_SERVER_CONFIG = pkg_resources.resource_filename(
@ -312,3 +314,33 @@ def fixture_add_comment(author=None, media_entry=None, comment=None):
return comment return comment
def fixture_add_comment_report(comment=None, reported_user=None,
reporter=None, created=None, report_content=None):
if comment is None:
comment = fixture_add_comment()
if reported_user is None:
reported_user = fixture_add_user()
if reporter is None:
reporter = fixture_add_user()
if created is None:
created=datetime.now()
if report_content is None:
report_content = \
'Auto-generated test report by user {0}'.format(
reporter)
comment_report = CommentReport(comment=comment,
reported_user = reported_user,
reporter = reporter,
created = created,
report_content=report_content)
comment_report.save()
Session.expunge(comment_report)
return comment_report

View File

@ -52,7 +52,8 @@ def render_400(request, err_msg=None):
_ = pass_to_ugettext _ = pass_to_ugettext
title = _("Bad Request") title = _("Bad Request")
if err_msg is None: if err_msg is None:
err_msg = _("The request sent to the server is invalid, please double check it") err_msg = _("The request sent to the server is invalid, \
please double check it")
return render_error(request, 400, title, err_msg) return render_error(request, 400, title, err_msg)
@ -78,9 +79,11 @@ def render_user_banned(request):
and the reason why they have been banned" and the reason why they have been banned"
""" """
user_ban = UserBan.query.get(request.user.id) user_ban = UserBan.query.get(request.user.id)
if datetime.now()>user_ban.expiration_date: if (user_ban.expiration_date is not None and
datetime.now()>user_ban.expiration_date):
user_ban.delete() user_ban.delete()
redirect(request, return redirect(request,
'index') 'index')
return render_to_response(request, return render_to_response(request,
'mediagoblin/banned.html', 'mediagoblin/banned.html',
@ -141,7 +144,7 @@ def json_response(serializable, _disable_cors=False, *args, **kw):
Any extra arguments and keyword arguments are passed to the Any extra arguments and keyword arguments are passed to the
Response.__init__ method. Response.__init__ method.
''' '''
response = wz_Response(json.dumps(serializable), *args, content_type='application/json', **kw) response = wz_Response(json.dumps(serializable), *args, content_type='application/json', **kw)
if not _disable_cors: if not _disable_cors:

View File

@ -19,7 +19,7 @@ from mediagoblin.tools.template import render_template
from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.base import Session from mediagoblin.db.base import Session
from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport, from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport,
MediaComment, MediaEntry) MediaComment, MediaEntry)
from mediagoblin.user_pages import forms as user_forms from mediagoblin.user_pages import forms as user_forms
@ -80,14 +80,14 @@ def add_media_to_collection(collection, media, note=None, commit=True):
def build_report_object(report_form, media_entry=None, comment=None): def build_report_object(report_form, media_entry=None, comment=None):
""" """
This function is used to convert a form object (from a User filing a This function is used to convert a form object (from a User filing a
report) into either a MediaReport or CommentReport object. report) into either a MediaReport or CommentReport object.
:param report_form should be a MediaReportForm or a CommentReportForm :param report_form should be a MediaReportForm or a CommentReportForm
object object
:param :param
:returns either of MediaReport or a CommentReport object that has not been :returns either of MediaReport or a CommentReport object that has not been
saved. In case of an improper form_dict, returns None saved. In case of an improper form_dict, returns None
""" """

View File

@ -162,6 +162,7 @@ def media_home(request, media, page, **kwargs):
@get_media_entry_by_id @get_media_entry_by_id
@require_active_login @require_active_login
@user_has_privilege(u'commenter')
def media_post_comment(request, media): def media_post_comment(request, media):
""" """
recieves POST from a MediaEntry() comment form, saves the comment. recieves POST from a MediaEntry() comment form, saves the comment.
@ -651,13 +652,13 @@ def file_a_report(request, media, comment=None):
'form':form} 'form':form}
if request.method == "POST": if request.method == "POST":
report_table = build_report_object(form, report_object = build_report_object(form,
media_entry=media, media_entry=media,
comment=comment) comment=comment)
# if the object was built successfully, report_table will not be None # if the object was built successfully, report_table will not be None
if report_table: if report_object:
report_table.save() report_object.save()
return redirect( return redirect(
request, request,
'index') 'index')
@ -671,5 +672,5 @@ def file_a_report(request, media, comment=None):
@require_active_login @require_active_login
@get_user_media_entry @get_user_media_entry
@get_media_comment_by_id @get_media_comment_by_id
def file_a_comment_report(request, media, comment): def file_a_comment_report(request, media, comment):
return file_a_report(request, comment=comment) return file_a_report(request, comment=comment)