|
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
|