Merge branch 'master' into OPW-Moderation-Update
authortilly-Q <nattilypigeonfowl@gmail.com>
Tue, 20 Aug 2013 16:21:13 +0000 (12:21 -0400)
committertilly-Q <nattilypigeonfowl@gmail.com>
Tue, 20 Aug 2013 16:21:13 +0000 (12:21 -0400)
Conflicts:
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/routing.py
mediagoblin/user_pages/views.py

14 files changed:
1  2 
mediagoblin/auth/views.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/decorators.py
mediagoblin/routing.py
mediagoblin/static/css/base.css
mediagoblin/submit/views.py
mediagoblin/templates/mediagoblin/base.html
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/tests/test_oauth2.py
mediagoblin/tools/response.py
mediagoblin/user_pages/forms.py
mediagoblin/user_pages/routing.py
mediagoblin/user_pages/views.py

Simple merge
index 9dff22ee7b531370388515087aa3e07488f50b11,374ab4c8f6b6bbba969d2b8ccf399cdb62931207..0eedc5d407bc05dd41c14f6c7e560375fcb5e7eb
@@@ -25,9 -25,10 +25,11 @@@ from sqlalchemy.ext.declarative import 
  from sqlalchemy.sql import and_
  from migrate.changeset.constraint import UniqueConstraint
  
+ from mediagoblin.db.extratypes import JSONEncoded
  from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
 -from mediagoblin.db.models import MediaEntry, Collection, User, MediaComment
 +from mediagoblin.db.models import (MediaEntry, Collection, User, 
 +                                   MediaComment, Privilege, ReportBase)
  
  MIGRATIONS = {}
  
@@@ -381,74 -380,83 +383,153 @@@ def pw_hash_nullable(db)
          constraint = UniqueConstraint('username', table=user_table)
          constraint.create()
  
 +class ReportBase_v0(declarative_base()):
 +    __tablename__ = 'core__reports'
 +    id = Column(Integer, primary_key=True)
 +    reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    report_content = Column(UnicodeText)
 +    reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    created = Column(DateTime, nullable=False, default=datetime.datetime.now) 
 +    discriminator = Column('type', Unicode(50))
 +    __mapper_args__ = {'polymorphic_on': discriminator}
 +
 +class CommentReport_v0(ReportBase_v0):
 +    __tablename__ = 'core__reports_on_comments'
 +    __mapper_args__ = {'polymorphic_identity': 'comment_report'}
 +
 +    id = Column('id',Integer, ForeignKey('core__reports.id'),
 +                                                primary_key=True)
 +    comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False)
 +
 +class MediaReport_v0(ReportBase_v0):
 +    __tablename__ = 'core__reports_on_media'
 +    __mapper_args__ = {'polymorphic_identity': 'media_report'}
 +
 +    id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
 +    media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
 +
 +class ArchivedReport_v0(ReportBase_v0):
 +    __tablename__ = 'core__reports_archived'
 +    __mapper_args__ = {'polymorphic_identity': 'archived_report'}
 +
 +    id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
 +    media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
 +    comment_id = Column(Integer, ForeignKey(MediaComment.id))
 +    resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
 +    resolved_time = Column(DateTime)
 +    result = Column(UnicodeText)
 +
 +class UserBan_v0(declarative_base()):
 +    __tablename__ = 'core__user_bans'
 +    user_id = Column('id',Integer, ForeignKey(User.id), nullable=False,
 +                                         primary_key=True)
 +    expiration_date = Column(DateTime)
 +    reason = Column(UnicodeText, nullable=False)
 +
 +class Privilege_v0(declarative_base()):
 +    __tablename__ = 'core__privileges'
 +    id = Column(Integer, nullable=False, primary_key=True, unique=True)
 +    privilege_name = Column(Unicode, nullable=False, unique=True)
 +
 +class PrivilegeUserAssociation_v0(declarative_base()):
 +    __tablename__ = 'core__privileges_users'
 +    group_id = Column(
 +        'core__privilege_id', 
 +        Integer, 
 +        ForeignKey(User.id), 
 +        primary_key=True)
 +    user_id = Column(
 +        'core__user_id', 
 +        Integer, 
 +        ForeignKey(Privilege.id), 
 +        primary_key=True)
 +
 +@RegisterMigration(14, MIGRATIONS)
 +def create_moderation_tables(db):
 +    ReportBase_v0.__table__.create(db.bind)
 +    CommentReport_v0.__table__.create(db.bind)
 +    MediaReport_v0.__table__.create(db.bind)
 +    ArchivedReport_v0.__table__.create(db.bind)
 +    UserBan_v0.__table__.create(db.bind)
 +    Privilege_v0.__table__.create(db.bind)
 +    PrivilegeUserAssociation_v0.__table__.create(db.bind)
      db.commit()
+ # oauth1 migrations
+ class Client_v0(declarative_base()):
+     """
+         Model representing a client - Used for API Auth
+     """
+     __tablename__ = "core__clients"
+     id = Column(Unicode, nullable=True, primary_key=True)
+     secret = Column(Unicode, nullable=False)
+     expirey = Column(DateTime, nullable=True)
+     application_type = Column(Unicode, nullable=False)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     # optional stuff
+     redirect_uri = Column(JSONEncoded, nullable=True)
+     logo_url = Column(Unicode, nullable=True)
+     application_name = Column(Unicode, nullable=True)
+     contacts = Column(JSONEncoded, nullable=True)
+     def __repr__(self):
+         if self.application_name:
+             return "<Client {0} - {1}>".format(self.application_name, self.id)
+         else:
+             return "<Client {0}>".format(self.id)
+ class RequestToken_v0(declarative_base()):
+     """
+         Model for representing the request tokens
+     """
+     __tablename__ = "core__request_tokens"
+     token = Column(Unicode, primary_key=True)
+     secret = Column(Unicode, nullable=False)
+     client = Column(Unicode, ForeignKey(Client_v0.id))
+     user = Column(Integer, ForeignKey(User.id), nullable=True)
+     used = Column(Boolean, default=False)
+     authenticated = Column(Boolean, default=False)
+     verifier = Column(Unicode, nullable=True)
+     callback = Column(Unicode, nullable=False, default=u"oob")
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     
+ class AccessToken_v0(declarative_base()):
+     """
+         Model for representing the access tokens
+     """
+     __tablename__ = "core__access_tokens"
+     token = Column(Unicode, nullable=False, primary_key=True)
+     secret = Column(Unicode, nullable=False)
+     user = Column(Integer, ForeignKey(User.id))
+     request_token = Column(Unicode, ForeignKey(RequestToken_v0.token))
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+  
+ class NonceTimestamp_v0(declarative_base()):
+     """
+         A place the timestamp and nonce can be stored - this is for OAuth1
+     """
+     __tablename__ = "core__nonce_timestamps"
+     nonce = Column(Unicode, nullable=False, primary_key=True)
+     timestamp = Column(DateTime, nullable=False, primary_key=True)
+ @RegisterMigration(14, MIGRATIONS)
+ def create_oauth1_tables(db):
+     """ Creates the OAuth1 tables """
+     Client_v0.__table__.create(db.bind)
+     RequestToken_v0.__table__.create(db.bind)
+     AccessToken_v0.__table__.create(db.bind)
+     NonceTimestamp_v0.__table__.create(db.bind)
+     db.commit()
index 69b59c996ee8cba07e6cc12629be8eb6f9f7a3bd,9cb39ff4821c4a8e622034d3a4341d0c2febec28..25b4fa8f6b0217ac28f922ff30a7a3805bc73d1d
@@@ -106,16 -104,72 +104,81 @@@ class User(Base, UserMixin)
          super(User, self).delete(**kwargs)
          _log.info('Deleted user "{0}" account'.format(self.username))
  
 +    def has_privilege(self,*priv_names):
 +        if len(priv_names) == 1:
 +            priv = Privilege.query.filter(
 +                Privilege.privilege_name==priv_names[0]).one()
 +            return (priv in self.all_privileges)
 +        elif len(priv_names) > 1:
 +            return self.has_privilege(priv_names[0]) or \
 +                self.has_privilege(*priv_names[1:])
 +        return False
  
+ class Client(Base):
+     """
+         Model representing a client - Used for API Auth
+     """
+     __tablename__ = "core__clients"
+     id = Column(Unicode, nullable=True, primary_key=True)
+     secret = Column(Unicode, nullable=False)
+     expirey = Column(DateTime, nullable=True)
+     application_type = Column(Unicode, nullable=False)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     # optional stuff
+     redirect_uri = Column(JSONEncoded, nullable=True)
+     logo_url = Column(Unicode, nullable=True)
+     application_name = Column(Unicode, nullable=True)
+     contacts = Column(JSONEncoded, nullable=True)
+     def __repr__(self):
+         if self.application_name:
+             return "<Client {0} - {1}>".format(self.application_name, self.id)
+         else:
+             return "<Client {0}>".format(self.id)
+ class RequestToken(Base):
+     """
+         Model for representing the request tokens
+     """
+     __tablename__ = "core__request_tokens"
+     token = Column(Unicode, primary_key=True)
+     secret = Column(Unicode, nullable=False)
+     client = Column(Unicode, ForeignKey(Client.id))
+     user = Column(Integer, ForeignKey(User.id), nullable=True)
+     used = Column(Boolean, default=False)
+     authenticated = Column(Boolean, default=False)
+     verifier = Column(Unicode, nullable=True)
+     callback = Column(Unicode, nullable=False, default=u"oob")
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ class AccessToken(Base):
+     """
+         Model for representing the access tokens
+     """
+     __tablename__ = "core__access_tokens"
+     token = Column(Unicode, nullable=False, primary_key=True)
+     secret = Column(Unicode, nullable=False)
+     user = Column(Integer, ForeignKey(User.id))
+     request_token = Column(Unicode, ForeignKey(RequestToken.token))
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ class NonceTimestamp(Base):
+     """
+         A place the timestamp and nonce can be stored - this is for OAuth1
+     """
+     __tablename__ = "core__nonce_timestamps"
+     nonce = Column(Unicode, nullable=False, primary_key=True)
+     timestamp = Column(DateTime, nullable=False, primary_key=True)
  
  class MediaEntry(Base, MediaEntryMixin):
      """
@@@ -768,16 -645,14 +832,15 @@@ with_polymorphic
      Notification,
      [ProcessingNotification, CommentNotification])
  
  MODELS = [
-     User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, 
-     MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData, ReportBase,
-     CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation,
-     ArchivedReport, Notification, CommentNotification, 
-       ProcessingNotification, CommentSubscription]
 -    User, Client, RequestToken, AccessToken, NonceTimestamp, MediaEntry, Tag,
 -    MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames,
 -    MediaAttachmentFile, ProcessingMetaData, Notification, CommentNotification,
 -    ProcessingNotification, CommentSubscription]
++    User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
++    MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData,
++    Notification, CommentNotification, ProcessingNotification,
++    CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan, 
++      Privilege, PrivilegeUserAssociation, ArchivedReport, ArchivedReport]
  
  """
-  Foundations are the default rows that are created immediately after the tables 
+  Foundations are the default rows that are created immediately after the tables
   are initialized. Each entry to  this dictionary should be in the format of:
                   ModelConstructorObject:List of Dictionaries
   (Each Dictionary represents a row on the Table to be created, containing each
index d3a9647ec914e9ea49c0b6518da7badf744ba4e3,685d0d98205e3a11908671e9c2177c273a86a8c1..a347916426f27747a31e695fc44ad5ee82847cc2
@@@ -22,11 -22,12 +22,14 @@@ from oauthlib.oauth1 import ResourceEnd
  
  from mediagoblin import mg_globals as mgg
  from mediagoblin import messages
 -from mediagoblin.db.models import MediaEntry, User
 -from mediagoblin.tools.response import json_response, redirect, render_404
 +from mediagoblin.db.models import (MediaEntry, User, MediaComment,
 +                                                      UserBan, Privilege)
- from mediagoblin.tools.response import redirect, render_404, render_user_banned
++from mediagoblin.tools.response import (redirect, render_404, 
++                                                              render_user_banned, json_response)
  from mediagoblin.tools.translate import pass_to_ugettext as _
  
+ from mediagoblin.oauth.tools.request import decode_authorization_header
+ from mediagoblin.oauth.oauth import GMGRequestValidator
  
  def require_active_login(controller):
      """
@@@ -291,49 -272,31 +306,72 @@@ def auth_enabled(controller)
  
      return wrapper
  
- def get_workbench(func):
-     """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
-     @wraps(func)
-     def new_func(*args, **kwargs):
-         with mgg.workbench_manager.create() as workbench:
-             return func(*args, workbench=workbench, **kwargs)
-     return new_func
 +def require_admin_or_moderator_login(controller):
 +    """
 +    Require an login from an administrator or a moderator.
 +    """
 +    @wraps(controller)
 +    def new_controller_func(request, *args, **kwargs):
 +        if request.user and \
 +            not request.user.has_privilege(u'admin',u'moderator'):
 +
 +            raise Forbidden()
 +        elif not request.user:
 +            next_url = urljoin(
 +                    request.urlgen('mediagoblin.auth.login',
 +                        qualified=True),
 +                    request.url)
 +
 +            return redirect(request, 'mediagoblin.auth.login',
 +                            next=next_url)
 +
 +        return controller(request, *args, **kwargs)
 +
 +    return new_controller_func
 +
++
 +def user_not_banned(controller):
 +    """
 +    Requires that the user has not been banned. Otherwise redirects to the page
 +    explaining why they have been banned
 +    """
 +    @wraps(controller)
 +    def wrapper(request, *args, **kwargs):
 +        if request.user:
 +            user_banned = UserBan.query.get(request.user.id)
 +            if user_banned:
 +                return render_user_banned(request)
 +        return controller(request, *args, **kwargs)
 +
 +    return wrapper
 +
++
++
+ def oauth_required(controller):
+     """ Used to wrap API endpoints where oauth is required """
+     @wraps(controller)
+     def wrapper(request, *args, **kwargs):
+         data = request.headers
+         authorization = decode_authorization_header(data)
+         if authorization == dict():
+             error = "Missing required parameter."
+             return json_response({"error": error}, status=400)
+          
+         request_validator = GMGRequestValidator()
+         resource_endpoint = ResourceEndpoint(request_validator)
+         valid, request = resource_endpoint.validate_protected_resource_request(
+                 uri=request.url,
+                 http_method=request.method,
+                 body=request.get_data(),
+                 headers=dict(request.headers),
+                 )
+         if not valid:
+             error = "Invalid oauth prarameter."
+             return json_response({"error": error}, status=400)
+         return controller(request, *args, **kwargs)
+     return wrapper
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index d83338e94a0e32e8296f9a051fe858ea79a3c8c8,ac8084c5125c6f6cf1504c0b0679b113627fc20b..eb786f47b6f51d16317610530956c4de9493f7df
@@@ -47,17 -47,5 +47,17 @@@ class MediaCollectForm(wtforms.Form)
      collection_description = wtforms.TextAreaField(
          _('Description of this collection'),
          description=_("""You can use
-                       <a href="http://daringfireball.net/projects/markdown/basics">
+                       <a href="http://daringfireball.net/projects/markdown/basics" target="_blank">
                        Markdown</a> for formatting."""))
 +
 +class CommentReportForm(wtforms.Form):
 +    report_reason = wtforms.TextAreaField(
 +        _('Reason for Reporting'),
 +        [wtforms.validators.Required()])
 +    reporter_id = wtforms.HiddenField('')
 +
 +class MediaReportForm(wtforms.Form):
 +    report_reason = wtforms.TextAreaField(
 +        _('Reason for Reporting'),
 +        [wtforms.validators.Required()])
 +    reporter_id = wtforms.HiddenField('')
Simple merge
index 6c0bada256512c7166533f9852d0ee653e10f317,49691a2990dee937dde385856400181cda7d56e3..00fcf282e0ce426f773491972f5de3633524c1dd
@@@ -19,10 -20,10 +20,11 @@@ import jso
  
  from mediagoblin import messages, mg_globals
  from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
 -                                   CollectionItem, User)
 +                                   CollectionItem, User, MediaComment,
 +                                   CommentReport, MediaReport)
  from mediagoblin.tools.response import render_to_response, render_404, \
      redirect, redirect_obj
+ from mediagoblin.tools.text import cleaned_markdown_conversion
  from mediagoblin.tools.translate import pass_to_ugettext as _
  from mediagoblin.tools.pagination import Pagination
  from mediagoblin.user_pages import forms as user_forms