Blame progit/__init__.py

Pierre-Yves Chibon 2088eb
#-*- coding: utf-8 -*-
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
"""
Pierre-Yves Chibon 2088eb
 (c) 2014 - Copyright Red Hat Inc
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
 Authors:
Pierre-Yves Chibon 2088eb
   Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
"""
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
## These two lines are needed to run on EL6
Pierre-Yves Chibon 2088eb
__requires__ = ['SQLAlchemy >= 0.7', 'jinja2 >= 2.4']
Pierre-Yves Chibon 2088eb
import pkg_resources
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
__version__ = '0.1'
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
import logging
Pierre-Yves Chibon 2088eb
import os
Pierre-Yves Chibon 2088eb
from logging.handlers import SMTPHandler
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
import arrow
Pierre-Yves Chibon 2088eb
import flask
Pierre-Yves Chibon d8f19f
import pygit2
Pierre-Yves Chibon 2088eb
from flask_fas_openid import FAS
Pierre-Yves Chibon 2088eb
from functools import wraps
Pierre-Yves Chibon 2088eb
from sqlalchemy.exc import SQLAlchemyError
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon d558c6
import progit.lib
Pierre-Yves Chibon 89e681
import progit.doc_utils
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
# Create the application.
Pierre-Yves Chibon 2088eb
APP = flask.Flask(__name__)
Pierre-Yves Chibon 76e062
APP.jinja_env.trim_blocks = True
Pierre-Yves Chibon 76e062
APP.jinja_env.lstrip_blocks = True
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
# set up FAS
Pierre-Yves Chibon 2088eb
APP.config.from_object('progit.default_config')
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
if 'PROGIT_CONFIG' in os.environ:
Pierre-Yves Chibon 2088eb
    APP.config.from_envvar('PROGIT_CONFIG')
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
FAS = FAS(APP)
Pierre-Yves Chibon d558c6
SESSION = progit.lib.create_session(APP.config['DB_URL'])
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
# Set up the logger
Pierre-Yves Chibon 2088eb
## Send emails for big exception
Pierre-Yves Chibon 2088eb
mail_handler = SMTPHandler(
Pierre-Yves Chibon 2088eb
    APP.config.get('SMTP_SERVER', '127.0.0.1'),
Pierre-Yves Chibon 2088eb
    'nobody@fedoraproject.org',
Pierre-Yves Chibon 2088eb
    APP.config.get('MAIL_ADMIN', APP.config['EMAIL_ERROR']),
Pierre-Yves Chibon 7d07b9
    'Progit error')
Pierre-Yves Chibon 2088eb
mail_handler.setFormatter(logging.Formatter('''
Pierre-Yves Chibon 2088eb
    Message type:       %(levelname)s
Pierre-Yves Chibon 2088eb
    Location:           %(pathname)s:%(lineno)d
Pierre-Yves Chibon 2088eb
    Module:             %(module)s
Pierre-Yves Chibon 2088eb
    Function:           %(funcName)s
Pierre-Yves Chibon 2088eb
    Time:               %(asctime)s
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    Message:
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    %(message)s
Pierre-Yves Chibon 2088eb
'''))
Pierre-Yves Chibon 2088eb
mail_handler.setLevel(logging.ERROR)
Pierre-Yves Chibon 2088eb
if not APP.debug:
Pierre-Yves Chibon 2088eb
    APP.logger.addHandler(mail_handler)
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
## Send classic logs into syslog
Pierre-Yves Chibon 2088eb
handler = logging.StreamHandler()
Pierre-Yves Chibon 2088eb
handler.setLevel(APP.config.get('log_level', 'INFO'))
Pierre-Yves Chibon 2088eb
APP.logger.addHandler(handler)
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
LOG = APP.logger
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
def authenticated():
Pierre-Yves Chibon 2088eb
    return hasattr(flask.g, 'fas_user') and flask.g.fas_user
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
def is_admin():
Pierre-Yves Chibon 2088eb
    """ Return whether the user is admin for this application or not. """
Pierre-Yves Chibon 2088eb
    if not authenticated() \
Pierre-Yves Chibon 2088eb
            or not flask.g.fas_user.cla_done \
Pierre-Yves Chibon 2088eb
            or len(flask.g.fas_user.groups) < 1:
Pierre-Yves Chibon 2088eb
        return False
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    admins = APP.config['ADMIN_GROUP']
Pierre-Yves Chibon 2088eb
    if isinstance(admins, basestring):
Pierre-Yves Chibon 2088eb
        admins = set([admins])
Pierre-Yves Chibon 2088eb
    else:  # pragma: no cover
Pierre-Yves Chibon 2088eb
        admins = set(admins)
Pierre-Yves Chibon 2088eb
    groups = set(flask.g.fas_user.groups)
Pierre-Yves Chibon 2088eb
    return not groups.isdisjoint(admins)
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon d5f37b
def is_repo_admin(repo_obj):
Pierre-Yves Chibon d5f37b
    """ Return whether the user is an admin of the provided repo. """
Pierre-Yves Chibon d5f37b
    if not authenticated() \
Pierre-Yves Chibon d5f37b
            or not flask.g.fas_user.cla_done:
Pierre-Yves Chibon d5f37b
        return False
Pierre-Yves Chibon d5f37b
Pierre-Yves Chibon d5f37b
    user = flask.g.fas_user.username
Pierre-Yves Chibon d5f37b
Pierre-Yves Chibon d5f37b
    return repo_obj.user or user in repo_obj.users
Pierre-Yves Chibon d5f37b
Pierre-Yves Chibon d5f37b
Pierre-Yves Chibon 2088eb
def cla_required(function):
Pierre-Yves Chibon 2088eb
    """ Flask decorator to retrict access to CLA signed user.
Pierre-Yves Chibon 2088eb
To use this decorator you need to have a function named 'auth_login'.
Pierre-Yves Chibon 2088eb
Without that function the redirect if the user is not logged in will not
Pierre-Yves Chibon 2088eb
work.
Pierre-Yves Chibon 2088eb
"""
Pierre-Yves Chibon 2088eb
    @wraps(function)
Pierre-Yves Chibon 2088eb
    def decorated_function(*args, **kwargs):
Pierre-Yves Chibon 2088eb
        """ Decorated function, actually does the work. """
Pierre-Yves Chibon 2088eb
        if not authenticated():
Pierre-Yves Chibon 2088eb
            return flask.redirect(
Pierre-Yves Chibon 2088eb
                flask.url_for('auth_login', next=flask.request.url))
Pierre-Yves Chibon 2088eb
        elif not flask.g.fas_user.cla_done:
Pierre-Yves Chibon 2088eb
            flask.flash('You must sign the CLA (Contributor License '
Pierre-Yves Chibon 767d71
                        'Agreement to use progit', 'errors')
Pierre-Yves Chibon 2088eb
            return flask.redirect(flask.url_for('.index'))
Pierre-Yves Chibon 2088eb
        return function(*args, **kwargs)
Pierre-Yves Chibon 2088eb
    return decorated_function
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
@APP.context_processor
Pierre-Yves Chibon 2088eb
def inject_variables():
Pierre-Yves Chibon 2088eb
    """ With this decorator we can set some variables to all templates.
Pierre-Yves Chibon 2088eb
    """
Pierre-Yves Chibon 2088eb
    user_admin = is_admin()
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    return dict(
Pierre-Yves Chibon 2088eb
        version=__version__,
Pierre-Yves Chibon 2088eb
        admin=user_admin,
Pierre-Yves Chibon b9c444
        authenticated=authenticated(),
Pierre-Yves Chibon 2088eb
    )
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 6428d8
Pierre-Yves Chibon 2088eb
@APP.template_filter('lastcommit_date')
Pierre-Yves Chibon 2088eb
def lastcommit_date_filter(repo):
Pierre-Yves Chibon 2088eb
    """ Template filter returning the last commit date of the provided repo.
Pierre-Yves Chibon 2088eb
    """
Pierre-Yves Chibon 671d31
    if not repo.is_empty:
Pierre-Yves Chibon 671d31
        commit = repo[repo.head.target]
Pierre-Yves Chibon 671d31
        return arrow.get(commit.commit_time).humanize()
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 6e710b
@APP.template_filter('humanize')
Pierre-Yves Chibon 6e710b
def humanize_date(date):
Pierre-Yves Chibon 6e710b
    """ Template filter returning the last commit date of the provided repo.
Pierre-Yves Chibon 6e710b
    """
Pierre-Yves Chibon 6e710b
    return arrow.get(date).humanize()
Pierre-Yves Chibon 6e710b
Pierre-Yves Chibon 6e710b
Pierre-Yves Chibon c560ab
@APP.template_filter('rst2html')
Pierre-Yves Chibon c560ab
def rst2html(rst_string):
Pierre-Yves Chibon c560ab
    """ Template filter transforming rst text into html
Pierre-Yves Chibon c560ab
    """
Pierre-Yves Chibon cbdf87
    if rst_string:
Pierre-Yves Chibon cbdf87
        return progit.doc_utils.convert_doc(unicode(rst_string))
Pierre-Yves Chibon c560ab
Pierre-Yves Chibon c560ab
Pierre-Yves Chibon 2088eb
@APP.route('/login/', methods=('GET', 'POST'))
Pierre-Yves Chibon 2088eb
def auth_login():
Pierre-Yves Chibon 2088eb
    """ Method to log into the application using FAS OpenID. """
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    return_point = flask.url_for('index')
Pierre-Yves Chibon 2088eb
    if 'next' in flask.request.args:
Pierre-Yves Chibon 2088eb
        return_point = flask.request.args['next']
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    if authenticated():
Pierre-Yves Chibon 2088eb
        return flask.redirect(return_point)
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
    return FAS.login(return_url=return_point)
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 2088eb
@APP.route('/logout/')
Pierre-Yves Chibon 2088eb
def auth_logout():
Pierre-Yves Chibon 2088eb
    """ Method to log out from the application. """
Pierre-Yves Chibon 2088eb
    if not authenticated():
Pierre-Yves Chibon 2088eb
        return flask.redirect(flask.url_for('index'))
Pierre-Yves Chibon 2088eb
    FAS.logout()
Pierre-Yves Chibon 2088eb
    flask.flash('You have been logged out')
Pierre-Yves Chibon 2088eb
    return flask.redirect(flask.url_for('index'))
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon d8f19f
Pierre-Yves Chibon d8f19f
def __get_file_in_tree(repo_obj, tree, filepath):
Pierre-Yves Chibon d8f19f
    ''' Retrieve the entry corresponding to the provided filename in a
Pierre-Yves Chibon d8f19f
    given tree.
Pierre-Yves Chibon d8f19f
    '''
Pierre-Yves Chibon d8f19f
    filename = filepath[0]
Pierre-Yves Chibon d8f19f
    if isinstance(tree, pygit2.Blob):
Pierre-Yves Chibon d8f19f
        return
Pierre-Yves Chibon d8f19f
    for el in tree:
Pierre-Yves Chibon d8f19f
        if el.name == filename:
Pierre-Yves Chibon d8f19f
            if len(filepath) == 1:
Pierre-Yves Chibon d8f19f
                return repo_obj[el.oid]
Pierre-Yves Chibon d8f19f
            else:
Pierre-Yves Chibon d8f19f
                return __get_file_in_tree(
Pierre-Yves Chibon d8f19f
                    repo_obj, repo_obj[el.oid], filepath[1:])
Pierre-Yves Chibon d8f19f
Pierre-Yves Chibon 2088eb
## Import the application
Pierre-Yves Chibon 2088eb
Pierre-Yves Chibon 47950c
import progit.app
Pierre-Yves Chibon 4b7a7d
import progit.docs
Pierre-Yves Chibon fac0b1
import progit.fork
Pierre-Yves Chibon 47950c
import progit.issues
Pierre-Yves Chibon 47950c
import progit.repo