Anonymous avatar Anonymous committed f60b1f8

first principal commit

Comments (0)

Files changed (9)

newsmeme/__init__.py

 
 from logging.handlers import SMTPHandler, RotatingFileHandler
 
-from flask import Flask, Response, session, request, g, jsonify
+from flask import Flask, Response, session, request, g, \
+    jsonify, redirect, url_for, flash
 
 from flaskext.babel import Babel, gettext as _
 from flaskext.themes import setup_themes
+from flaskext.principal import Principal, RoleNeed, UserNeed, identity_loaded
 
 from newsmeme import views
 from newsmeme import helpers
 RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'
 
 SQLALCHEMY_DATABASE_URI = "sqlite:///newsmeme.db"
-SQLALCHEMY_ECHO = True
+SQLALCHEMY_ECHO = False
 
 ADMINS = ()
 
 
 THEME = 'newsmeme'
 
+def configure_logging(app):
+    if app.debug:
+        return
+
+    mail_handler = \
+        SMTPHandler(app.config['MAIL_SERVER'],
+                   'error@newsmeme.com',
+                   app.config['ADMINS'], 
+                    'Application error',
+                    (
+                        app.config['MAIL_USERNAME'],
+                        app.config['MAIL_PASSWORD'],
+                    ))
+
+    mail_handler.setLevel(logging.ERROR)
+    app.logger.addHandler(mail_handler)
+
+    formatter = logging.Formatter(
+        '%(asctime)s %(levelname)s: %(message)s '
+        '[in %(pathname)s:%(lineno)d]')
+
+    debug_log = os.path.join(
+        app.root_path, app.config['DEBUG_LOG'])
+
+    debug_file_handler = \
+        RotatingFileHandler(debug_log,
+                            maxBytes=100000,
+                            backupCount=10)
+
+    debug_file_handler.setLevel(logging.DEBUG)
+    debug_file_handler.setFormatter(formatter)
+    app.logger.addHandler(debug_file_handler)
+
+    error_log = os.path.join(
+        app.root_path, app.config['ERROR_LOG'])
+
+    error_file_handler = \
+        RotatingFileHandler(error_log,
+                            maxBytes=100000,
+                            backupCount=10)
+
+    error_file_handler.setLevel(logging.ERROR)
+    error_file_handler.setFormatter(formatter)
+    app.logger.addHandler(error_file_handler)
+
+
 def create_app(config=None):
 
     app = Flask(__name__)
     app.config.from_envvar('APP_CONFIG', silent=True)
 
     # configure logging
+
+    configure_logging(app)
     
-    if not app.debug:
-        mail_handler = \
-            SMTPHandler(app.config['MAIL_SERVER'],
-                       'error@newsmeme.com',
-                       app.config['ADMINS'], 
-                        'Application error',
-                        (
-                            app.config['MAIL_USERNAME'],
-                            app.config['MAIL_PASSWORD'],
-                        ))
-
-        mail_handler.setLevel(logging.ERROR)
-        app.logger.addHandler(mail_handler)
-
-        formatter = logging.Formatter(
-            '%(asctime)s %(levelname)s: %(message)s '
-            '[in %(pathname)s:%(lineno)d]')
-
-        debug_log = os.path.join(
-            app.root_path, app.config['DEBUG_LOG'])
-
-        debug_file_handler = \
-            RotatingFileHandler(debug_log,
-                                maxBytes=100000,
-                                backupCount=10)
-    
-        debug_file_handler.setLevel(logging.DEBUG)
-        debug_file_handler.setFormatter(formatter)
-        app.logger.addHandler(debug_file_handler)
-
-        error_log = os.path.join(
-            app.root_path, app.config['ERROR_LOG'])
-
-        error_file_handler = \
-            RotatingFileHandler(error_log,
-                                maxBytes=100000,
-                                backupCount=10)
-
-        error_file_handler.setLevel(logging.ERROR)
-        error_file_handler.setFormatter(formatter)
-        app.logger.addHandler(error_file_handler)
-
-
     # configure extensions
 
     mail.init_app(app)
+
     db.init_app(app)
+
     oid.init_app(app)
 
     babel = Babel(app)
 
     setup_themes(app)
 
+    principal = Principal(app)
+
+    @identity_loaded.connect_via(app)
+    def on_identity_loaded(sender, identity):
+        if identity.name:
+            user = User.query.get(identity.name)
+        else:
+            user = None
+        if user:
+            identity.provides.add(RoleNeed('auth'))
+            identity.provides.add(UserNeed(user.id))
+            
+            if user.is_moderator:
+                identity.provides.add(RoleNeed('moderator'))
+
+            if user.is_admin:
+                identity.provides.add(RoleNeed('admin'))
+        identity.user = user
 
     # filters
 
 
     # error handlers
 
-    if not app.config['TESTING']:
+    if not app.testing:
         @app.errorhandler(404)
         def page_not_found(error):
             if request.is_xhr:
         @app.errorhandler(500)
         def server_error(error):
             if request.is_xhr:
-                return Response(error_('Sorry, an error has occurred'))
+                return jsonify(error=_('Sorry, an error has occurred'))
             return render_template("errors/500.html", error=error)
 
-    # before handlers
+    @app.errorhandler(401)
+    def unauthorized(error):
+        if request.is_xhr:
+            return jsonfiy(error=_("Login required"))
+        flash(_("Please login to see this page"), "error")
+        return redirect(url_for("account.login", next=request.path))
+
+    # before handler
 
     @app.before_request
     def authenticate():
-        g.user = None
-        if 'user_id' in session:
-            g.user = User.query.get(session['user_id'])
-            if g.user is None:
-                session.pop('user_id')
-        
+        g.user = getattr(g.identity, 'user', None)
+
     # babel setup
 
     @babel.localeselector

newsmeme/models.py

 from flask import Markup, url_for
 
 from flaskext.sqlalchemy import BaseQuery
+from flaskext.principal import UserNeed, RoleNeed, Permission, Denial
 
 from newsmeme import signals
 from newsmeme.helpers import slugify, domain
 from newsmeme.extensions import db
 from newsmeme.types import DenormalizedText
+from newsmeme.permissions import moderator, auth
 
 _markdown = functools.partial(markdown.markdown,
                               safe_mode='remove',
     
     __mapper_args__ = {'order_by' : id.desc()}
 
+    class Permissions(object):
+
+        def __init__(self, obj):
+            self.obj = obj
+
+        @cached_property
+        def view(self):
+
+            if self.obj.access == Post.PUBLIC:
+                return Permission()
+
+            permission = moderator & Permission(UserNeed(self.obj.author_id))
+
+            if self.obj.access == Post.FRIENDS:
+                needs = [UserNeed(user_id) for user_id in \
+                            self.obj.author.friends]
+
+                permission = permission & Permission(*needs)
+
+            return permission
+      
+      
+        @cached_property
+        def vote(self):
+            permission = auth & Denial(UserNeed(self.obj.author_id))
+            needs = [UserNeed(user_id) for user_id in self.obj.votes]
+            permission = permission & Denial(*needs)
+            
+            return permission
+
+        @cached_property
+        def comment(self):
+            return self.view
+
+        @cached_property
+        def edit(self):
+            return moderator & Permission(UserNeed(self.obj.author_id))
+
+        @cached_property
+        def delete(self):
+            return self.edit
+
     def __init__(self, *args, **kwargs):
         super(Post, self).__init__(*args, **kwargs)
         self.votes = self.votes or set()
                 for tag in self.taglist]
 
     @cached_property
+    def permissions(self):
+        return Post.Permissions(self)
+
+    @cached_property
     def domain(self):
         if not self.link:
             return ''

newsmeme/permissions.py

+from flaskext.principal import RoleNeed, Permission
+
+__all__ = ['admin', 'moderator', 'auth']
+
+admin = Permission(RoleNeed('admin'))
+moderator = Permission(RoleNeed('moderator'))
+auth = Permission(RoleNeed('auth'))

newsmeme/templates/macros/_post.html

 {% endmacro %}
 
 {% macro render_post(post) %}
-<h3>{% if post.can_vote(g.user) %}
+<h3>{% if post.permissions.vote %}
 <span id="vote-{{ post.id }}">
     <a href="#" onclick="newsmeme.vote_post('{{ url_for('post.upvote', post_id=post.id) }}'); return false;"><img src="{{ theme_static("images/up-icon.png") }}"></a>
     <a href="#" onclick="newsmeme.vote_post('{{ url_for('post.downvote', post_id=post.id) }}'); return false;"><img src="{{ theme_static("images/down-icon.png") }}"></a>  

newsmeme/templates/post/post.html

 
 {% block content %}
 <h2>
-{% if post.can_vote(g.user) %}
+{% if post.permissions.vote %}
 <span id="vote-{{ post.id }}">
     <a href="#" onclick="newsmeme.vote_post('{{ url_for('post.upvote', post_id=post.id) }}'); return false;"><img src="{{ theme_static("images/up-icon.png") }}"></a>
     <a href="#" onclick="newsmeme.vote_post('{{ url_for('post.downvote', post_id=post.id) }}'); return false;"><img src="{{ theme_static("images/down-icon.png") }}"></a>  
 {{ _('Posted') }} {{ post.date_created|timesince }} {{ _('by') }} 
 <a href="{{ url_for('user.posts', username=post.author.username) }}">{{ post.author.username }}</a> 
 | <a href="{{ post.permalink }}">permalink</a> 
-    {% if g.user %}
-    {% if post.can_edit(g.user) %}
+    {% if post.permissions.edit %}
     | <a href="{{ url_for('post.edit', post_id=post.id) }}">edit this post</a> 
-    {% if post.can_delete(g.user) %}
+    {% endif %}
+    {% if post.permissions.delete %}
     | <a href="#" onclick="$('#delete-post').toggle(); return false;">delete this post</a> 
     <div id="delete-post" style="display:none;">
         <strong>{{ _('Are you sure you want to delete this post ?') }}</strong>
         <a href="#" onclick="$('#delete-post').toggle(); return false;">{{ _('no') }}</a>
     </div>
     {% endif %}
-    {% endif %}
+    {% if post.permissions.comment %}
     <h3>{{ _('Add a comment') }}</h3>
     <form id="comment-form" method="POST" action="{{ url_for('post.add_comment', post_id=post.id) }}">
         {{ comment_form.csrf_token }}

newsmeme/views/account.py

 import uuid
 
-from flask import Module, flash, request, g, \
+from flask import Module, flash, request, g, current_app, \
     abort, redirect, url_for, session, jsonify
 
 from flaskext.mail import Message
 from flaskext.babel import gettext as _
+from flaskext.principal import Identity, identity_changed
 
 from newsmeme.forms import ChangePasswordForm, EditAccountForm, \
     DeleteAccountForm
 
         if user and authenticated:
             session.permanent = form.remember.data
-            session['user_id'] = user.id
-            
+            identity_changed.send(current_app._get_current_object(), 
+                                  identity=Identity(user.id))
+
             # check if openid has been passed in
             openid = session.pop('openid', None)
             if openid:
         db.session.add(user)
         db.session.commit()
 
-        session['user_id'] = user.id
+        identity_changed.send(current_app._get_current_object(), 
+                              identity=Identity(user.id))
 
         flash(_("Welcome, %(name)s", name=user.username), "success")
 
 @account.route("/logout/")
 def logout():
 
-    session.pop('user_id', None)
+    identity_changed.send(current_app._get_current_object(), 
+                          identity=Identity(None))
     flash(_("You are now logged out"), "success")
     return redirect(url_for('frontend.index'))
 
 
     if form.validate_on_submit():
 
-        session.pop('user_id')
-        
+        identity_changed.send(current_app._get_current_object(), 
+                              identity=Identity(None))
+
         db.session.delete(g.user)
         db.session.commit()
 

newsmeme/views/frontend.py

 from newsmeme.extensions import mail, db
 from newsmeme.helpers import render_template
 from newsmeme.forms import PostForm, ContactForm
+from newsmeme.permissions import auth
 from newsmeme.decorators import login_required, keep_login_url
 
 PER_PAGE = 20
 
 
 @frontend.route("/submit/", methods=("GET", "POST"))
-@login_required
+@auth.require(401)
 def submit():
 
     form = PostForm()

newsmeme/views/openid.py

 from flask import Module, redirect, url_for, session, flash, \
-    abort, request
+    abort, request, current_app
 
 from flaskext.babel import gettext as _
+from flaskext.principal import Identity, identity_changed
 
 from newsmeme.models import User
 from newsmeme.helpers import slugify, render_template
                                 email=response.email))
 
     if authenticated:
-        session['user_id'] = user.id
         session.permanent = True
+        identity_changed.send(current_app._get_current_object(),
+                              identity=Identity(user.id))
         
         flash(_("Welcome back, %%s") % user.username, "success")
         
         db.session.add(user)
         db.session.commit()
 
-        session['user_id'] = user.id
         session.permanent = True
 
+        identity_changed.send(current_app._get_current_object(),
+                              identity=Identity(user.id))
+
         flash(_("Welcome, %%s") % user.username, "success")
 
         next_url = form.next.data or \

newsmeme/views/post.py

-from flask import Module, abort, jsonify, request,  \
-    g, url_for, redirect, flash
+from flask import Module, abort, jsonify, g, url_for, redirect, flash
 
 from flaskext.mail import Message
 from flaskext.babel import gettext as _
 @keep_login_url
 def view(post_id, slug=None):
     post = Post.query.get_or_404(post_id)
-    if not post.can_access(g.user):
-        if not g.user:
-            flash(_("You must be logged in to see this post"), "error")
-            return redirect(url_for("account.login", next=request.path))
-        else:
-            flash(_("You must be a friend to see this post"), "error")
-            abort(403)
+    post.permissions.view.test(403)
 
     def edit_comment_form(comment):
         return CommentForm(obj=comment)
 @login_required
 def add_comment(post_id, parent_id=None):
     post = Post.query.get_or_404(post_id)
-    if not post.can_access(g.user):
-        abort(403)
+    post.permissions.comment.test(403)
 
     parent = Comment.query.get_or_404(parent_id) if parent_id else None
     
 def edit(post_id):
 
     post = Post.query.get_or_404(post_id)
+    post.permissions.edit.test(403)
     
-    if not post.can_edit(g.user):
-        abort(403)
-
     form = PostForm(obj=post)
     if form.validate_on_submit():
 
 def delete(post_id):
 
     post = Post.query.get_or_404(post_id)
+    post.permissions.delete.test(403)
     
-    if not post.can_delete(g.user):
-        abort(403)
-
     Comment.query.filter_by(post=post).delete()
 
     db.session.delete(post)
 def _vote(post_id, score):
 
     post = Post.query.get_or_404(post_id)
-    
-    if not post.can_vote(g.user):
-        abort(403)
+    post.permissions.vote.test(403)    
 
     post.score += score
     post.author.karma += score
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.