Changeset - aa51aca7fd1a
[Not reviewed]
stable
0 3 0
Valentin Kleibel - 19 months ago 2024-08-26 21:13:06
valentin@vrvis.at
Grafted from: 8daf3500d52f
controller: Handle UnicodeDecodeError from webob decoding invalid URLs

webob will try to utf-8 decode all %-encoded bytes in URL-parameters, but will
not handle Unicode erors ... and neither did Kallithea. Visiting a URL like
http://localhost:5000/?%AD would thus give an unhandled exception showing
"Internal Server Error" to the user, and logging the full traceback and:

WebApp Error: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xad in position 0: invalid start byte

This has been seen a lot recently from attackers probing for a php
vulnerability
https://devco.re/blog/2024/06/06/security-alert-cve-2024-4577-php-cgi-argument-injection-vulnerability-en/ .

Now handle these exceptions more nicely and reject with "400 Bad Request".
3 files changed with 11 insertions and 1 deletions:
0 comments (0 inline, 0 general)
CONTRIBUTORS
Show inline comments
 
List of contributors to Kallithea project:
 

	
 
    Mads Kiilerich <mads@kiilerich.com> 2016-2024
 
    Aristotelis Stageiritis <aristotelis79@gmail.com> 2024
 
    Poesty Li <poesty7450@gmail.com> 2024
 
    Valentin Kleibel <valentin@vrvis.at> 2024
 
    Manuel Jacob <me@manueljacob.de> 2019-2020 2022-2023
 
    Mathias De Mare <mathias.de_mare@nokia.com> 2023
 
    qy117121 <mixuan121@gmail.com> 2023
 
    Asterios Dimitriou <steve@pci.gr> 2016-2017 2020 2022
 
    Étienne Gilli <etienne@gilli.io> 2020-2022
 
    Jaime Marquínez Ferrándiz <weblate@jregistros.fastmail.net> 2022
 
    Louis Bertrand <louis.bertrand@durhamcollege.ca> 2022
 
    toras9000 <toras9000@gmail.com> 2022
 
    yzqzss <yzqzss@othing.xyz> 2022
 
    МАН69К <weblate@mah69k.net> 2022
 
    Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2021
 
    ssantos <ssantos@web.de> 2018-2021
 
    Private <adamantine.sword@gmail.com> 2019-2021
 
    fresh <fresh190@protonmail.com> 2020-2021
 
    robertus <robertuss12@gmail.com> 2020-2021
 
    Eugenia Russell <eugenia.russell2019@gmail.com> 2021
 
    Michalis <michalisntovas@yahoo.gr> 2021
 
    vs <vsuhachev@yandex.ru> 2021
 
    Александр <akonn7@mail.ru> 2021
 
    Allan Nordhøy <epost@anotheragency.no> 2017-2020
 
    Anton Schur <tonich.sh@gmail.com> 2017 2020
 
    Artem <kovalevartem.ru@gmail.com> 2020
 
    David Ignjić <ignjic@gmail.com> 2020
 
    Dennis Fink <dennis.fink@c3l.lu> 2020
 
    J. Lavoie <j.lavoie@net-c.ca> 2020
 
    Ross Thomas <ross@lns-nevasoft.com> 2020
 
    Tim Ooms <tatankat@users.noreply.github.com> 2020
 
    Andrej Shadura <andrew@shadura.me> 2012 2014-2017 2019
 
    Étienne Gilli <etienne.gilli@gmail.com> 2015-2017 2019
 
    Adi Kriegisch <adi@cg.tuwien.ac.at> 2019
 
    Danni Randeris <danniranderis@gmail.com> 2019
 
    Edmund Wong <ewong@crazy-cat.org> 2019
 
    Elizabeth Sherrock <lizzyd710@gmail.com> 2019
 
    Hüseyin Tunç <huseyin.tunc@bulutfon.com> 2019
 
    leela <53352@protonmail.com> 2019
 
    Mateusz Mendel <mendelm9@gmail.com> 2019
 
    Nathan <bonnemainsnathan@gmail.com> 2019
 
    Oleksandr Shtalinberg <o.shtalinberg@gmail.com> 2019
 
    THANOS SIOURDAKIS <siourdakisthanos@gmail.com> 2019
 
    Wolfgang Scherer <wolfgang.scherer@gmx.de> 2019
 
    Христо Станев <hstanev@gmail.com> 2019
 
    Dominik Ruf <dominikruf@gmail.com> 2012 2014-2018
 
    Michal Čihař <michal@cihar.com> 2014-2015 2018
 
    Branko Majic <branko@majic.rs> 2015 2018
 
    Chris Rule <crule@aegistg.com> 2018
 
    Jesús Sánchez <jsanchezfdz95@gmail.com> 2018
 
    Patrick Vane <patrick_vane@lowentry.com> 2018
 
    Pheng Heong Tan <phtan90@gmail.com> 2018
 
    Максим Якимчук <xpinovo@gmail.com> 2018
 
    Марс Ямбар <mjambarmeta@gmail.com> 2018
 
    Mads Kiilerich <madski@unity3d.com> 2012-2017
 
    Unity Technologies 2012-2017
 
    Søren Løvborg <sorenl@unity3d.com> 2015-2017
 
    Sam Jaques <sam.jaques@me.com> 2015 2017
 
    Alessandro Molina <alessandro.molina@axant.it> 2017
 
    Ching-Chen Mao <mao@lins.fju.edu.tw> 2017
 
    Eivind Tagseth <eivindt@gmail.com> 2017
 
    FUJIWARA Katsunori <foozy@lares.dti.ne.jp> 2017
 
    Holger Schramm <info@schramm.by> 2017
 
    Karl Goetz <karl@kgoetz.id.au> 2017
 
    Lars Kruse <devel@sumpfralle.de> 2017
 
    Marko Semet <markosemet@googlemail.com> 2017
 
    Viktar Vauchkevich <victorenator@gmail.com> 2017
 
    Takumi IINO <trot.thunder@gmail.com> 2012-2016
 
    Jan Heylen <heyleke@gmail.com> 2015-2016
 
    Robert Martinez <ntttq@inboxen.org> 2015-2016
 
    Robert Rauch <mail@robertrauch.de> 2015-2016
 
    Angel Ezquerra <angel.ezquerra@gmail.com> 2016
 
    Anton Shestakov <av6@dwimlabs.net> 2016
 
    Brandon Jones <bjones14@gmail.com> 2016
 
    Kateryna Musina <kateryna@unity3d.com> 2016
 
    Konstantin Veretennicov <kveretennicov@gmail.com> 2016
 
    Oscar Curero <oscar@naiandei.net> 2016
 
    Robert James Dennington <tinytimrob@googlemail.com> 2016
 
    timeless@gmail.com 2016
 
    YFdyh000 <yfdyh000@gmail.com> 2016
 
    Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
 
    Sean Farley <sean.michael.farley@gmail.com> 2013-2015
 
    Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014-2015
 
    Christian Oyarzun <oyarzun@gmail.com> 2014-2015
 
    Joseph Rivera <rivera.d.joseph@gmail.com> 2014-2015
 
    Anatoly Bubenkov <bubenkoff@gmail.com> 2015
 
    Andrew Bartlett <abartlet@catalyst.net.nz> 2015
 
    Balázs Úr <urbalazs@gmail.com> 2015
 
    Ben Finney <ben@benfinney.id.au> 2015
 
    Daniel Hobley <danielh@unity3d.com> 2015
 
    David Avigni <david.avigni@ankapi.com> 2015
 
    Denis Blanchette <dblanchette@coveo.com> 2015
 
    duanhongyi <duanhongyi@doopai.com> 2015
 
    EriCSN Chang <ericsning@gmail.com> 2015
 
    Grzegorz Krason <grzegorz.krason@gmail.com> 2015
 
    Jiří Suchan <yed@vanyli.net> 2015
 
    Kazunari Kobayashi <kobanari@nifty.com> 2015
 
    Kevin Bullock <kbullock@ringworld.org> 2015
 
    kobanari <kobanari@nifty.com> 2015
 
    Marc Abramowitz <marc@marc-abramowitz.com> 2015
 
    Marc Villetard <marc.villetard@gmail.com> 2015
 
    Matthias Zilk <matthias.zilk@gmail.com> 2015
 
    Michael Pohl <michael@mipapo.de> 2015
 
    Michael V. DePalatis <mike@depalatis.net> 2015
 
    Morten Skaaning <mortens@unity3d.com> 2015
 
    Nick High <nick@silverchip.org> 2015
 
    Niemand Jedermann <predatorix@web.de> 2015
 
    Peter Vitt <petervitt@web.de> 2015
 
    Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> 2015
 
    Tuux <tuxa@galaxie.eu.org> 2015
 
    Viktar Palstsiuk <vipals@gmail.com> 2015
 
    Ante Ilic <ante@unity3d.com> 2014
 
    Calinou <calinou@opmbx.org> 2014
 
    Daniel Anderson <daniel@dattrix.com> 2014
 
    Henrik Stuart <hg@hstuart.dk> 2014
 
    Ingo von Borstel <kallithea@planetmaker.de> 2014
 
    invision70 <invision70@gmail.com> 2014
 
    Jelmer Vernooij <jelmer@samba.org> 2014
 
    Jim Hague <jim.hague@acm.org> 2014
 
    Matt Fellows <kallithea@matt-fellows.me.uk> 2014
 
    Max Roman <max@choloclos.se> 2014
 
    Na'Tosha Bard <natosha@unity3d.com> 2014
 
    Rasmus Selsmark <rasmuss@unity3d.com> 2014
 
    SkryabinD <skryabind@gmail.com> 2014
 
    Tim Freund <tim@freunds.net> 2014
 
    Travis Burtrum <android@moparisthebest.com> 2014
 
    whosaysni <whosaysni@gmail.com> 2014
 
    Zoltan Gyarmati <mr.zoltan.gyarmati@gmail.com> 2014
 
    Marcin Kuźmiński <marcin@python-works.com> 2010-2013
 
    Nemcio <areczek01@gmail.com> 2012-2013
 
    xpol <xpolife@gmail.com> 2012-2013
 
    Andrey Mivrenik <myvrenik@gmail.com> 2013
 
    Aparkar <aparkar@icloud.com> 2013
 
    ArcheR <aleclitvinov1980@gmail.com> 2013
 
    Dennis Brakhane <brakhane@googlemail.com> 2013
 
    gnustavo <gustavo@gnustavo.com> 2013
 
    Grzegorz Rożniecki <xaerxess@gmail.com> 2013
 
    Ilya Beda <ir4y.ix@gmail.com> 2013
 
    ivlevdenis <ivlevdenis.ru@gmail.com> 2013
 
    Jonathan Sternberg <jonathansternberg@gmail.com> 2013
 
    Leonardo Carneiro <leonardo@unity3d.com> 2013
 
    Magnus Ericmats <magnus.ericmats@gmail.com> 2013
 
    Martin Vium <martinv@unity3d.com> 2013
 
    Mikhail Zholobov <legal90@gmail.com> 2013
 
    mokeev1995 <mokeev_andre@mail.ru> 2013
 
    Ruslan Bekenev <furyinbox@gmail.com> 2013
 
    shirou - しろう 2013
 
    Simon Lopez <simon.lopez@slopez.org> 2013
 
    softforwinxp <softforwinxp@gmail.com> 2013
 
    stephanj <info@stephan-jauernick.de> 2013
 
    Ton Plomp <tcplomp@gmail.com> 2013
 
    zhmylove <zhmylove@narod.ru> 2013
 
    こいんとす <tkondou@gmail.com> 2013
 
    Augusto Herrmann <augusto.herrmann@planejamento.gov.br> 2011-2012
 
    Augusto Herrmann <augusto.herrmann@gmail.com> 2012
 
    Dan Sheridan <djs@adelard.com> 2012
 
    Dies Koper <diesk@fast.au.fujitsu.com> 2012
 
    Erwin Kroon <e.kroon@smartmetersolutions.nl> 2012
 
    H Waldo G <gwaldo@gmail.com> 2012
 
    hppj <hppj@postmage.biz> 2012
 
    Indra Talip <indra.talip@gmail.com> 2012
 
    mikespook <mikespook@gmail.com> 2012
 
    nansenat16 <nansenat16@null.tw> 2012
 
    Nemcio <bogdan114@g.pl> 2012
 
    Philip Jameson <philip.j@hostdime.com> 2012
 
    Raoul Thill <raoul.thill@gmail.com> 2012
 
    Stefan Engel <mail@engel-stefan.de> 2012
 
    Tony Bussieres <t.bussieres@gmail.com> 2012
 
    Vincent Caron <vcaron@bearstech.com> 2012
 
    Vincent Duvert <vincent@duvert.net> 2012
 
    Vladislav Poluhin <nuklea@gmail.com> 2012
 
    Zachary Auclair <zach101@gmail.com> 2012
 
    Ankit Solanki <ankit.solanki@gmail.com> 2011
 
    Dmitri Kuznetsov 2011
 
    Jared Bunting <jared.bunting@peachjean.com> 2011
 
    Jason Harris <jason@jasonfharris.com> 2011
 
    Les Peabody <lpeabody@gmail.com> 2011
 
    Liad Shani <liadff@gmail.com> 2011
 
    Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it> 2011
 
    Matt Zuba <matt.zuba@goodwillaz.org> 2011
 
    Nicolas VINOT <aeris@imirhil.fr> 2011
 
    Shawn K. O'Shea <shawn@eth0.net> 2011
 
    Thayne Harbaugh <thayne@fusionio.com> 2011
 
    Łukasz Balcerzak <lukaszbalcerzak@gmail.com> 2010
 
    Andrew Kesterson <andrew@aklabs.net>
 
    cejones
 
    David A. Sjøen <david.sjoen@westcon.no>
 
    James Rhodes <jrhodes@redpointsoftware.com.au>
 
    Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
 
    larikale
 
    RhodeCode GmbH
 
    Sebastian Kreutzberger <sebastian@rhodecode.com>
 
    Steve Romanow <slestak989@gmail.com>
 
    SteveCohen
 
    Thomas <thomas@rhodecode.com>
 
    Thomas Waldmann <tw-public@gmx.de>
kallithea/controllers/base.py
Show inline comments
 
@@ -267,367 +267,375 @@ class BaseVCSController(object):
 
        return user, None
 

	
 
    def _handle_request(self, environ, start_response):
 
        raise NotImplementedError()
 

	
 
    def _check_permission(self, action, authuser, repo_name):
 
        """
 
        :param action: 'push' or 'pull'
 
        :param user: `AuthUser` instance
 
        :param repo_name: repository name
 
        """
 
        if action == 'push':
 
            if not HasPermissionAnyMiddleware('repository.write',
 
                                              'repository.admin')(authuser,
 
                                                                  repo_name):
 
                return False
 

	
 
        elif action == 'pull':
 
            #any other action need at least read permission
 
            if not HasPermissionAnyMiddleware('repository.read',
 
                                              'repository.write',
 
                                              'repository.admin')(authuser,
 
                                                                  repo_name):
 
                return False
 

	
 
        else:
 
            assert False, action
 

	
 
        return True
 

	
 
    def __call__(self, environ, start_response):
 
        try:
 
            # try parsing a request for this VCS - if it fails, call the wrapped app
 
            parsed_request = self.parse_request(environ)
 
            if parsed_request is None:
 
                return self.application(environ, start_response)
 

	
 
            # skip passing error to error controller
 
            environ['pylons.status_code_redirect'] = True
 

	
 
            # quick check if repo exists...
 
            if not is_valid_repo(parsed_request.repo_name, self.basepath, self.scm_alias):
 
                raise webob.exc.HTTPNotFound()
 

	
 
            if parsed_request.action is None:
 
                # Note: the client doesn't get the helpful error message
 
                raise webob.exc.HTTPBadRequest('Unable to detect pull/push action for %r! Are you using a nonstandard command or client?' % parsed_request.repo_name)
 

	
 
            #======================================================================
 
            # CHECK PERMISSIONS
 
            #======================================================================
 
            ip_addr = get_ip_addr(environ)
 
            user, response_app = self._authorize(environ, parsed_request.action, parsed_request.repo_name, ip_addr)
 
            if response_app is not None:
 
                return response_app(environ, start_response)
 

	
 
            #======================================================================
 
            # REQUEST HANDLING
 
            #======================================================================
 
            set_hook_environment(user.username, ip_addr,
 
                parsed_request.repo_name, self.scm_alias, parsed_request.action)
 

	
 
            try:
 
                log.info('%s action on %s repo "%s" by "%s" from %s',
 
                         parsed_request.action, self.scm_alias, parsed_request.repo_name, user.username, ip_addr)
 
                app = self._make_app(parsed_request)
 
                return app(environ, start_response)
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                raise webob.exc.HTTPInternalServerError()
 

	
 
        except webob.exc.HTTPException as e:
 
            return e(environ, start_response)
 

	
 

	
 
class BaseController(TGController):
 

	
 
    def _before(self, *args, **kwargs):
 
        """
 
        _before is called before controller methods and after __call__
 
        """
 
        if request.needs_csrf_check:
 
            # CSRF protection: Whenever a request has ambient authority (whether
 
            # through a session cookie or its origin IP address), it must include
 
            # the correct token, unless the HTTP method is GET or HEAD (and thus
 
            # guaranteed to be side effect free. In practice, the only situation
 
            # where we allow side effects without ambient authority is when the
 
            # authority comes from an API key; and that is handled above.
 
            token = request.POST.get(webutils.session_csrf_secret_name)
 
            if not token or token != webutils.session_csrf_secret_token():
 
                log.error('CSRF check failed')
 
                raise webob.exc.HTTPForbidden()
 

	
 
        c.kallithea_version = kallithea.__version__
 
        settings = db.Setting.get_app_settings()
 

	
 
        # Visual options
 
        c.visual = AttributeDict({})
 

	
 
        ## DB stored
 
        c.visual.show_public_icon = asbool(settings.get('show_public_icon'))
 
        c.visual.show_private_icon = asbool(settings.get('show_private_icon'))
 
        c.visual.stylify_metalabels = asbool(settings.get('stylify_metalabels'))
 
        c.visual.page_size = safe_int(settings.get('dashboard_items', 100))
 
        c.visual.admin_grid_items = safe_int(settings.get('admin_grid_items', 100))
 
        c.visual.repository_fields = asbool(settings.get('repository_fields'))
 
        c.visual.show_version = asbool(settings.get('show_version'))
 
        c.visual.use_gravatar = asbool(settings.get('use_gravatar'))
 
        c.visual.gravatar_url = settings.get('gravatar_url')
 

	
 
        c.ga_code = settings.get('ga_code')
 
        # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
 
        if c.ga_code and '<' not in c.ga_code:
 
            c.ga_code = '''<script type="text/javascript">
 
                var _gaq = _gaq || [];
 
                _gaq.push(['_setAccount', '%s']);
 
                _gaq.push(['_trackPageview']);
 

	
 
                (function() {
 
                    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
 
                    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
 
                    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
 
                    })();
 
            </script>''' % c.ga_code
 
        c.site_name = settings.get('title')
 
        c.clone_uri_tmpl = settings.get('clone_uri_tmpl') or db.Repository.DEFAULT_CLONE_URI
 
        c.clone_ssh_tmpl = settings.get('clone_ssh_tmpl') or db.Repository.DEFAULT_CLONE_SSH
 

	
 
        ## INI stored
 
        c.visual.allow_repo_location_change = asbool(config.get('allow_repo_location_change', True))
 
        c.visual.allow_custom_hooks_settings = asbool(config.get('allow_custom_hooks_settings', True))
 
        c.ssh_enabled = asbool(config.get('ssh_enabled', False))
 

	
 
        c.instance_id = config.get('instance_id')
 
        c.issues_url = config.get('bugtracker', url('issues_url'))
 
        # END CONFIG VARS
 

	
 
        c.repo_name = get_repo_slug(request)  # can be empty
 
        c.backends = list(kallithea.BACKENDS)
 

	
 
        self.cut_off_limit = safe_int(config.get('cut_off_limit'))
 

	
 
        c.my_pr_count = db.PullRequest.query(reviewer_id=request.authuser.user_id, include_closed=False).count()
 

	
 
        self.scm_model = ScmModel()
 

	
 
    @staticmethod
 
    def _determine_auth_user(session_authuser, ip_addr):
 
        """
 
        Create an `AuthUser` object given the API key/bearer token
 
        (if any) and the value of the authuser session cookie.
 
        Returns None if no valid user is found (like not active or no access for IP).
 
        """
 

	
 
        # Authenticate by session cookie
 
        # In ancient login sessions, 'authuser' may not be a dict.
 
        # In that case, the user will have to log in again.
 
        # v0.3 and earlier included an 'is_authenticated' key; if present,
 
        # this must be True.
 
        if isinstance(session_authuser, dict) and session_authuser.get('is_authenticated', True):
 
            return AuthUser.from_cookie(session_authuser, ip_addr=ip_addr)
 

	
 
        # Authenticate by auth_container plugin (if enabled)
 
        if any(
 
            plugin.is_container_auth
 
            for plugin in auth_modules.get_auth_plugins()
 
        ):
 
            try:
 
                user_info = auth_modules.authenticate('', '', request.environ)
 
            except UserCreationError as e:
 
                webutils.flash(e, 'error', logf=log.error)
 
            else:
 
                if user_info is not None:
 
                    username = user_info['username']
 
                    user = db.User.get_by_username(username, case_insensitive=True)
 
                    return log_in_user(user, remember=False, is_external_auth=True, ip_addr=ip_addr)
 

	
 
        # User is default user (if active) or anonymous
 
        default_user = db.User.get_default_user()
 
        authuser = AuthUser.make(dbuser=default_user, ip_addr=ip_addr)
 
        if authuser is None: # fall back to anonymous
 
            authuser = AuthUser(dbuser=default_user) # TODO: somehow use .make?
 
        return authuser
 

	
 
    @staticmethod
 
    def _basic_security_checks():
 
        """Perform basic security/sanity checks before processing the request."""
 

	
 
        # Only allow the following HTTP request methods.
 
        if request.method not in ['GET', 'HEAD', 'POST']:
 
            raise webob.exc.HTTPMethodNotAllowed()
 

	
 
        try:
 
            params = request.params
 
        except UnicodeDecodeError as e:
 
            # webobj will leak UnicodeDecodeError when decoding invalid
 
            # URLencoded byte sequences in parameters
 
            log.error('Error decoding request parameters: %s' % e)
 
            raise webob.exc.HTTPBadRequest()
 

	
 
        # Also verify the _method override - no longer allowed.
 
        if request.params.get('_method') is None:
 
        if params.get('_method') is None:
 
            pass # no override, no problem
 
        else:
 
            raise webob.exc.HTTPMethodNotAllowed()
 

	
 
        # Make sure CSRF token never appears in the URL. If so, invalidate it.
 
        if webutils.session_csrf_secret_name in request.GET:
 
            log.error('CSRF key leak detected')
 
            session.pop(webutils.session_csrf_secret_name, None)
 
            session.save()
 
            webutils.flash(_('CSRF token leak has been detected - all form tokens have been expired'),
 
                    category='error')
 

	
 
        # WebOb already ignores request payload parameters for anything other
 
        # than POST/PUT, but double-check since other Kallithea code relies on
 
        # this assumption.
 
        if request.method not in ['POST', 'PUT'] and request.POST:
 
            log.error('%r request with payload parameters; WebOb should have stopped this', request.method)
 
            raise webob.exc.HTTPBadRequest()
 

	
 
    def __call__(self, environ, context):
 
        try:
 
            ip_addr = get_ip_addr(environ)
 
            self._basic_security_checks()
 

	
 
            api_key = request.GET.get('api_key')
 
            try:
 
                # Request.authorization may raise ValueError on invalid input
 
                type, params = request.authorization
 
            except (ValueError, TypeError):
 
                pass
 
            else:
 
                if type.lower() == 'bearer':
 
                    api_key = params # bearer token is an api key too
 

	
 
            if api_key is None:
 
                authuser = self._determine_auth_user(
 
                    session.get('authuser'),
 
                    ip_addr=ip_addr,
 
                )
 
                needs_csrf_check = request.method not in ['GET', 'HEAD']
 

	
 
            else:
 
                dbuser = db.User.get_by_api_key(api_key)
 
                if dbuser is None:
 
                    log.info('No db user found for authentication with API key ****%s from %s',
 
                             api_key[-4:], ip_addr)
 
                authuser = AuthUser.make(dbuser=dbuser, is_external_auth=True, ip_addr=ip_addr)
 
                needs_csrf_check = False # API key provides CSRF protection
 

	
 
            if authuser is None:
 
                log.info('No valid user found')
 
                raise webob.exc.HTTPForbidden()
 

	
 
            # set globals for auth user
 
            request.authuser = authuser
 
            request.ip_addr = ip_addr
 
            request.needs_csrf_check = needs_csrf_check
 

	
 
            log.info('IP: %s User: %s Request: %s',
 
                request.ip_addr, request.authuser,
 
                get_path_info(environ),
 
            )
 
            return super(BaseController, self).__call__(environ, context)
 
        except webob.exc.HTTPException as e:
 
            return e
 

	
 

	
 
class BaseRepoController(BaseController):
 
    """
 
    Base class for controllers responsible for loading all needed data for
 
    repository loaded items are
 

	
 
    c.db_repo_scm_instance: instance of scm repository
 
    c.db_repo: instance of db
 
    c.repository_followers: number of followers
 
    c.repository_forks: number of forks
 
    c.repository_following: weather the current user is following the current repo
 
    """
 

	
 
    def _before(self, *args, **kwargs):
 
        super(BaseRepoController, self)._before(*args, **kwargs)
 
        if c.repo_name:  # extracted from request by base-base BaseController._before
 
            _dbr = db.Repository.get_by_repo_name(c.repo_name)
 
            if not _dbr:
 
                return
 

	
 
            log.debug('Found repository in database %s with state `%s`',
 
                      _dbr, _dbr.repo_state)
 
            route = getattr(request.environ.get('routes.route'), 'name', '')
 

	
 
            # allow to delete repos that are somehow damages in filesystem
 
            if route in ['delete_repo']:
 
                return
 

	
 
            if _dbr.repo_state in [db.Repository.STATE_PENDING]:
 
                if route in ['repo_creating_home']:
 
                    return
 
                check_url = url('repo_creating_home', repo_name=c.repo_name)
 
                raise webob.exc.HTTPFound(location=check_url)
 

	
 
            dbr = c.db_repo = _dbr
 
            c.db_repo_scm_instance = c.db_repo.scm_instance
 
            if c.db_repo_scm_instance is None:
 
                log.error('%s this repository is present in database but it '
 
                          'cannot be created as an scm instance', c.repo_name)
 
                webutils.flash(_('Repository not found in the filesystem'),
 
                        category='error')
 
                raise webob.exc.HTTPNotFound()
 

	
 
            # some globals counter for menu
 
            c.repository_followers = self.scm_model.get_followers(dbr)
 
            c.repository_forks = self.scm_model.get_forks(dbr)
 
            c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
 
            c.repository_following = self.scm_model.is_following_repo(
 
                                    c.repo_name, request.authuser.user_id)
 

	
 
    @staticmethod
 
    def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
 
        """
 
        Safe way to get changeset. If error occurs show error.
 
        """
 
        try:
 
            return repo.scm_instance.get_ref_revision(ref_type, ref_name)
 
        except EmptyRepositoryError as e:
 
            if returnempty:
 
                return repo.scm_instance.EMPTY_CHANGESET
 
            webutils.flash(_('There are no changesets yet'), category='error')
 
            raise webob.exc.HTTPNotFound()
 
        except ChangesetDoesNotExistError as e:
 
            webutils.flash(_('Changeset for %s %s not found in %s') %
 
                              (ref_type, ref_name, repo.repo_name),
 
                    category='error')
 
            raise webob.exc.HTTPNotFound()
 
        except RepositoryError as e:
 
            log.error(traceback.format_exc())
 
            webutils.flash(e, category='error')
 
            raise webob.exc.HTTPBadRequest()
 

	
 

	
 
@decorator.decorator
 
def jsonify(func, *args, **kwargs):
 
    """Action decorator that formats output for JSON
 

	
 
    Given a function that will return content, this decorator will turn
 
    the result into JSON, with a content-type of 'application/json' and
 
    output it.
 
    """
 
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
 
    data = func(*args, **kwargs)
 
    if isinstance(data, (list, tuple)):
 
        # A JSON list response is syntactically valid JavaScript and can be
 
        # loaded and executed as JavaScript by a malicious third-party site
 
        # using <script>, which can lead to cross-site data leaks.
 
        # JSON responses should therefore be scalars or objects (i.e. Python
 
        # dicts), because a JSON object is a syntax error if intepreted as JS.
 
        msg = "JSON responses with Array envelopes are susceptible to " \
 
              "cross-site data leak attacks, see " \
 
              "https://web.archive.org/web/20120519231904/http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
 
        warnings.warn(msg, Warning, 2)
 
        log.warning(msg)
 
    log.debug("Returning JSON wrapped action output")
 
    return ascii_bytes(ext_json.dumps(data))
 

	
 
@decorator.decorator
 
def IfSshEnabled(func, *args, **kwargs):
 
    """Decorator for functions that can only be called if SSH access is enabled.
 

	
 
    If SSH access is disabled in the configuration file, HTTPNotFound is raised.
 
    """
 
    if not c.ssh_enabled:
 
        webutils.flash(_("SSH access is disabled."), category='warning')
 
        raise webob.exc.HTTPNotFound()
 
    return func(*args, **kwargs)
kallithea/templates/about.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 
<%block name="title">
 
    ${_('About')}
 
</%block>
 
<%block name="header_menu">
 
    ${self.menu('about')}
 
</%block>
 
<%def name="main()">
 

	
 
<div class="panel panel-primary">
 
  <div class="panel-heading">
 
    <h5 class="panel-title">${_('About')} Kallithea</h5>
 
  </div>
 

	
 
  <div class="panel-body panel-about">
 
  <p><a href="https://kallithea-scm.org/">Kallithea</a> is a project of the
 
  <a href="http://sfconservancy.org/">Software Freedom Conservancy, Inc.</a>
 
  and is released under the terms of the
 
  <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License,
 
  v 3.0 (GPLv3)</a>.</p>
 

	
 
  <p>Kallithea is copyrighted by various authors, including but not
 
  necessarily limited to the following:</p>
 
  <ul>
 

	
 
  <li>Copyright &copy; 2012&ndash;2024, Mads Kiilerich</li>
 
  <li>Copyright &copy; 2024, Aristotelis Stageiritis</li>
 
  <li>Copyright &copy; 2024, Poesty Li</li>
 
  <li>Copyright &copy; 2024, Valentin Kleibel</li>
 
  <li>Copyright &copy; 2019&ndash;2020, 2022&ndash;2023, Manuel Jacob</li>
 
  <li>Copyright &copy; 2023, Mathias De Mare</li>
 
  <li>Copyright &copy; 2023, qy117121</li>
 
  <li>Copyright &copy; 2015&ndash;2017, 2019&ndash;2022, Étienne Gilli</li>
 
  <li>Copyright &copy; 2016&ndash;2017, 2020, 2022, Asterios Dimitriou</li>
 
  <li>Copyright &copy; 2022, Jaime Marquínez Ferrándiz</li>
 
  <li>Copyright &copy; 2022, Louis Bertrand</li>
 
  <li>Copyright &copy; 2022, toras9000</li>
 
  <li>Copyright &copy; 2022, yzqzss</li>
 
  <li>Copyright &copy; 2022, МАН69К</li>
 
  <li>Copyright &copy; 2014&ndash;2021, Thomas De Schampheleire</li>
 
  <li>Copyright &copy; 2018&ndash;2021, ssantos</li>
 
  <li>Copyright &copy; 2019&ndash;2021, Private</li>
 
  <li>Copyright &copy; 2020&ndash;2021, fresh</li>
 
  <li>Copyright &copy; 2020&ndash;2021, robertus</li>
 
  <li>Copyright &copy; 2021, Eugenia Russell</li>
 
  <li>Copyright &copy; 2021, Michalis</li>
 
  <li>Copyright &copy; 2021, vs</li>
 
  <li>Copyright &copy; 2021, Александр</li>
 
  <li>Copyright &copy; 2017&ndash;2020, Allan Nordhøy</li>
 
  <li>Copyright &copy; 2017, 2020, Anton Schur</li>
 
  <li>Copyright &copy; 2020, Artem</li>
 
  <li>Copyright &copy; 2020, David Ignjić</li>
 
  <li>Copyright &copy; 2020, Dennis Fink</li>
 
  <li>Copyright &copy; 2020, J. Lavoie</li>
 
  <li>Copyright &copy; 2020, Ross Thomas</li>
 
  <li>Copyright &copy; 2020, Tim Ooms</li>
 
  <li>Copyright &copy; 2012, 2014&ndash;2017, 2019, Andrej Shadura</li>
 
  <li>Copyright &copy; 2019, Adi Kriegisch</li>
 
  <li>Copyright &copy; 2019, Danni Randeris</li>
 
  <li>Copyright &copy; 2019, Edmund Wong</li>
 
  <li>Copyright &copy; 2019, Elizabeth Sherrock</li>
 
  <li>Copyright &copy; 2019, Hüseyin Tunç</li>
 
  <li>Copyright &copy; 2019, leela</li>
 
  <li>Copyright &copy; 2019, Mateusz Mendel</li>
 
  <li>Copyright &copy; 2019, Nathan</li>
 
  <li>Copyright &copy; 2019, Oleksandr Shtalinberg</li>
 
  <li>Copyright &copy; 2019, THANOS SIOURDAKIS</li>
 
  <li>Copyright &copy; 2019, Wolfgang Scherer</li>
 
  <li>Copyright &copy; 2019, Христо Станев</li>
 
  <li>Copyright &copy; 2012, 2014&ndash;2018, Dominik Ruf</li>
 
  <li>Copyright &copy; 2014&ndash;2015, 2018, Michal Čihař</li>
 
  <li>Copyright &copy; 2015, 2018, Branko Majic</li>
 
  <li>Copyright &copy; 2018, Chris Rule</li>
 
  <li>Copyright &copy; 2018, Jesús Sánchez</li>
 
  <li>Copyright &copy; 2018, Patrick Vane</li>
 
  <li>Copyright &copy; 2018, Pheng Heong Tan</li>
 
  <li>Copyright &copy; 2018, Максим Якимчук</li>
 
  <li>Copyright &copy; 2018, Марс Ямбар</li>
 
  <li>Copyright &copy; 2012&ndash;2017, Unity Technologies</li>
 
  <li>Copyright &copy; 2015&ndash;2017, Søren Løvborg</li>
 
  <li>Copyright &copy; 2015, 2017, Sam Jaques</li>
 
  <li>Copyright &copy; 2017, Alessandro Molina</li>
 
  <li>Copyright &copy; 2017, Ching-Chen Mao</li>
 
  <li>Copyright &copy; 2017, Eivind Tagseth</li>
 
  <li>Copyright &copy; 2017, FUJIWARA Katsunori</li>
 
  <li>Copyright &copy; 2017, Holger Schramm</li>
 
  <li>Copyright &copy; 2017, Karl Goetz</li>
 
  <li>Copyright &copy; 2017, Lars Kruse</li>
 
  <li>Copyright &copy; 2017, Marko Semet</li>
 
  <li>Copyright &copy; 2017, Viktar Vauchkevich</li>
 
  <li>Copyright &copy; 2012&ndash;2016, Takumi IINO</li>
 
  <li>Copyright &copy; 2015&ndash;2016, Jan Heylen</li>
 
  <li>Copyright &copy; 2015&ndash;2016, Robert Martinez</li>
 
  <li>Copyright &copy; 2015&ndash;2016, Robert Rauch</li>
 
  <li>Copyright &copy; 2016, Angel Ezquerra</li>
 
  <li>Copyright &copy; 2016, Anton Shestakov</li>
 
  <li>Copyright &copy; 2016, Brandon Jones</li>
 
  <li>Copyright &copy; 2016, Kateryna Musina</li>
 
  <li>Copyright &copy; 2016, Konstantin Veretennicov</li>
 
  <li>Copyright &copy; 2016, Oscar Curero</li>
 
  <li>Copyright &copy; 2016, Robert James Dennington</li>
 
  <li>Copyright &copy; 2016, timeless@gmail.com</li>
 
  <li>Copyright &copy; 2016, YFdyh000</li>
 
  <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
 
  <li>Copyright &copy; 2014&ndash;2015, Bradley M. Kuhn</li>
 
  <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
 
  <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
 
  <li>Copyright &copy; 2014&ndash;2015, Sean Farley</li>
 
  <li>Copyright &copy; 2015, Anatoly Bubenkov</li>
 
  <li>Copyright &copy; 2015, Andrew Bartlett</li>
 
  <li>Copyright &copy; 2015, Balázs Úr</li>
 
  <li>Copyright &copy; 2015, Ben Finney</li>
 
  <li>Copyright &copy; 2015, Daniel Hobley</li>
 
  <li>Copyright &copy; 2015, David Avigni</li>
 
  <li>Copyright &copy; 2015, Denis Blanchette</li>
 
  <li>Copyright &copy; 2015, duanhongyi</li>
 
  <li>Copyright &copy; 2015, EriCSN Chang</li>
 
  <li>Copyright &copy; 2015, Grzegorz Krason</li>
 
  <li>Copyright &copy; 2015, Jiří Suchan</li>
 
  <li>Copyright &copy; 2015, Kazunari Kobayashi</li>
 
  <li>Copyright &copy; 2015, Kevin Bullock</li>
 
  <li>Copyright &copy; 2015, kobanari</li>
 
  <li>Copyright &copy; 2015, Marc Abramowitz</li>
 
  <li>Copyright &copy; 2015, Marc Villetard</li>
 
  <li>Copyright &copy; 2015, Matthias Zilk</li>
 
  <li>Copyright &copy; 2015, Michael Pohl</li>
 
  <li>Copyright &copy; 2015, Michael V. DePalatis</li>
 
  <li>Copyright &copy; 2015, Morten Skaaning</li>
 
  <li>Copyright &copy; 2015, Nick High</li>
 
  <li>Copyright &copy; 2015, Niemand Jedermann</li>
 
  <li>Copyright &copy; 2015, Peter Vitt</li>
 
  <li>Copyright &copy; 2015, Ronny Pfannschmidt</li>
 
  <li>Copyright &copy; 2015, Tuux</li>
 
  <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
 
  <li>Copyright &copy; 2014, Ante Ilic</li>
 
  <li>Copyright &copy; 2014, Calinou</li>
 
  <li>Copyright &copy; 2014, Daniel Anderson</li>
 
  <li>Copyright &copy; 2014, Henrik Stuart</li>
 
  <li>Copyright &copy; 2014, Ingo von Borstel</li>
 
  <li>Copyright &copy; 2014, invision70</li>
 
  <li>Copyright &copy; 2014, Jelmer Vernooij</li>
 
  <li>Copyright &copy; 2014, Jim Hague</li>
 
  <li>Copyright &copy; 2014, Matt Fellows</li>
 
  <li>Copyright &copy; 2014, Max Roman</li>
 
  <li>Copyright &copy; 2014, Na'Tosha Bard</li>
 
  <li>Copyright &copy; 2014, Rasmus Selsmark</li>
 
  <li>Copyright &copy; 2014, SkryabinD</li>
 
  <li>Copyright &copy; 2014, Tim Freund</li>
 
  <li>Copyright &copy; 2014, Travis Burtrum</li>
 
  <li>Copyright &copy; 2014, whosaysni</li>
 
  <li>Copyright &copy; 2014, Zoltan Gyarmati</li>
 
  <li>Copyright &copy; 2010&ndash;2013, Marcin Kuźmiński</li>
 
  <li>Copyright &copy; 2010&ndash;2013, RhodeCode GmbH</li>
 
  <li>Copyright &copy; 2011, 2013, Aparkar</li>
 
  <li>Copyright &copy; 2012&ndash;2013, Nemcio</li>
 
  <li>Copyright &copy; 2012&ndash;2013, xpol</li>
 
  <li>Copyright &copy; 2013, Andrey Mivrenik</li>
 
  <li>Copyright &copy; 2013, ArcheR</li>
 
  <li>Copyright &copy; 2013, Dennis Brakhane</li>
 
  <li>Copyright &copy; 2013, gnustavo</li>
 
  <li>Copyright &copy; 2013, Grzegorz Rożniecki</li>
 
  <li>Copyright &copy; 2013, Ilya Beda</li>
 
  <li>Copyright &copy; 2013, ivlevdenis</li>
 
  <li>Copyright &copy; 2013, Jonathan Sternberg</li>
 
  <li>Copyright &copy; 2013, Leonardo Carneiro</li>
 
  <li>Copyright &copy; 2013, Magnus Ericmats</li>
 
  <li>Copyright &copy; 2013, Martin Vium</li>
 
  <li>Copyright &copy; 2013, Mikhail Zholobov</li>
 
  <li>Copyright &copy; 2013, mokeev1995</li>
 
  <li>Copyright &copy; 2013, Ruslan Bekenev</li>
 
  <li>Copyright &copy; 2013, shirou - しろう</li>
 
  <li>Copyright &copy; 2013, Simon Lopez</li>
 
  <li>Copyright &copy; 2013, softforwinxp</li>
 
  <li>Copyright &copy; 2013, stephanj</li>
 
  <li>Copyright &copy; 2013, zhmylove</li>
 
  <li>Copyright &copy; 2013, こいんとす</li>
 
  <li>Copyright &copy; 2011&ndash;2012, Augusto Herrmann</li>
 
  <li>Copyright &copy; 2012, Dan Sheridan</li>
 
  <li>Copyright &copy; 2012, H Waldo G</li>
 
  <li>Copyright &copy; 2012, hppj</li>
 
  <li>Copyright &copy; 2012, Indra Talip</li>
 
  <li>Copyright &copy; 2012, mikespook</li>
 
  <li>Copyright &copy; 2012, nansenat16</li>
 
  <li>Copyright &copy; 2012, Philip Jameson</li>
 
  <li>Copyright &copy; 2012, Raoul Thill</li>
 
  <li>Copyright &copy; 2012, Tony Bussieres</li>
 
  <li>Copyright &copy; 2012, Vincent Duvert</li>
 
  <li>Copyright &copy; 2012, Vladislav Poluhin</li>
 
  <li>Copyright &copy; 2012, Zachary Auclair</li>
 
  <li>Copyright &copy; 2011, Ankit Solanki</li>
 
  <li>Copyright &copy; 2011, Dmitri Kuznetsov</li>
 
  <li>Copyright &copy; 2011, Jared Bunting</li>
 
  <li>Copyright &copy; 2011, Jason Harris</li>
 
  <li>Copyright &copy; 2011, Les Peabody</li>
 
  <li>Copyright &copy; 2011, Liad Shani</li>
 
  <li>Copyright &copy; 2011, Lorenzo M. Catucci</li>
 
  <li>Copyright &copy; 2011, Matt Zuba</li>
 
  <li>Copyright &copy; 2011, Nicolas VINOT</li>
 
  <li>Copyright &copy; 2011, Shawn K. O'Shea</li>
 
  <li>Copyright &copy; 2010, Łukasz Balcerzak</li>
 

	
 
## We did not list the following copyright holders, given that they appeared
 
## to use for-profit company affiliations in their contribution in the
 
## Mercurial log and therefore I didn't know if copyright was theirs or
 
## their company's.
 
## Copyright &copy; 2011 Thayne Harbaugh <thayne@fusionio.com>
 
## Copyright &copy; 2012 Dies Koper <diesk@fast.au.fujitsu.com>
 
## Copyright &copy; 2012 Erwin Kroon <e.kroon@smartmetersolutions.nl>
 
## Copyright &copy; 2012 Vincent Caron <vcaron@bearstech.com>
 
##
 
## These contributors' contributions may not be copyrightable:
 
## philip.j@hostdime.com in 2012
 
## Stefan Engel <mail@engel-stefan.de> in 2012
 
## Ton Plomp <tcplomp@gmail.com> in 2013
 
##
 
  </ul>
 

	
 
  <p>The above are the copyright holders who have submitted direct
 
  contributions to the Kallithea repository.</p>
 

	
 
  <p>In the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea
0 comments (0 inline, 0 general)