Changeset - 1948ede028ef
kallithea/__init__.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.__init__
 
~~~~~~~~~~~~~~~~~~
 

	
 
RhodeCode, a web based repository management based on pylons
 
versioning implementation: http://www.python.org/dev/peps/pep-0386/
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 9, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import sys
 
import platform
 

	
 
VERSION = (2, 2, 5)
 
BACKENDS = {
 
    'hg': 'Mercurial repository',
 
    'git': 'Git repository',
 
}
 

	
 
CELERY_ON = False
 
CELERY_EAGER = False
 

	
 
# link to config for pylons
 
CONFIG = {}
 

	
 
# Linked module for extensions
 
EXTENSIONS = {}
 

	
 
try:
 
    from kallithea.lib import get_current_revision
 
    _rev = get_current_revision(quiet=True)
 
    if _rev and len(VERSION) > 3:
 
        VERSION += ('%s' % _rev[0],)
 
except ImportError:
 
    pass
 

	
 
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
 
__dbversion__ = 31  # defines current db version for migrations
 
__platform__ = platform.system()
 
__license__ = 'GPLv3'
 
__py_version__ = sys.version_info
 
__author__ = 'RhodeCode GmbH'
 
__author__ = "Various Authors"
 
__url__ = 'https://kallithea-scm.org/'
 

	
 
is_windows = __platform__ in ['Windows']
 
is_unix = not is_windows
 

	
 
if len(VERSION) > 3:
 
    __version__ += '.'+VERSION[3]
 

	
 
    if len(VERSION) > 4:
 
        __version__ += VERSION[4]
 
    else:
 
        __version__ += '0'
kallithea/bin/__init__.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.__init__
 
~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Binary scripts for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Jun 03, 2012
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
kallithea/bin/base.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.base
 
~~~~~~~~~~~~~~~~~~
 

	
 
Base utils for shell scripts
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: May 09, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import os
 
import sys
 
import random
 
import urllib2
 
import pprint
 

	
 
try:
 
    from kallithea.lib.ext_json import json
 
except ImportError:
 
    try:
 
        import simplejson as json
 
    except ImportError:
 
        import json
 

	
 
CONFIG_NAME = '.config/kallithea'
 
FORMAT_PRETTY = 'pretty'
 
FORMAT_JSON = 'json'
 

	
 

	
 
def api_call(apikey, apihost, method=None, **kw):
 
    """
 
    Api_call wrapper for RhodeCode.
 

	
 
    :param apikey:
 
    :param apihost:
 
    :param format: formatting, pretty means prints and pprint of json
 
     json returns unparsed json
 
    :param method:
 
    :returns: json response from server
 
    """
 
    def _build_data(random_id):
 
        """
 
        Builds API data with given random ID
 

	
 
        :param random_id:
 
        """
 
        return {
 
            "id": random_id,
 
            "api_key": apikey,
 
            "method": method,
 
            "args": kw
 
        }
 

	
 
    if not method:
 
        raise Exception('please specify method name !')
 
    apihost = apihost.rstrip('/')
 
    id_ = random.randrange(1, 9999)
 
    req = urllib2.Request('%s/_admin/api' % apihost,
 
                      data=json.dumps(_build_data(id_)),
 
                      headers={'content-type': 'text/plain'})
 
    ret = urllib2.urlopen(req)
 
    raw_json = ret.read()
 
    json_data = json.loads(raw_json)
 
    id_ret = json_data['id']
 
    if id_ret == id_:
 
        return json_data
 

	
 
    else:
 
        _formatted_json = pprint.pformat(json_data)
 
        raise Exception('something went wrong. '
 
                        'ID mismatch got %s, expected %s | %s' % (
 
                                            id_ret, id_, _formatted_json))
 

	
 

	
 
class RcConf(object):
 
    """
 
    RhodeCode config for API
 

	
 
    conf = RcConf()
 
    conf['key']
 

	
 
    """
 

	
 
    def __init__(self, config_location=None, autoload=True, autocreate=False,
 
                 config=None):
 
        HOME = os.getenv('HOME', os.getenv('USERPROFILE')) or ''
 
        HOME_CONF = os.path.abspath(os.path.join(HOME, CONFIG_NAME))
 
        self._conf_name = HOME_CONF if not config_location else config_location
 
        self._conf = {}
 
        if autocreate:
 
            self.make_config(config)
 
        if autoload:
 
            self._conf = self.load_config()
 

	
 
    def __getitem__(self, key):
 
        return self._conf[key]
 

	
 
    def __nonzero__(self):
 
        if self._conf:
 
            return True
 
        return False
 

	
 
    def __eq__(self):
 
        return self._conf.__eq__()
 

	
 
    def __repr__(self):
 
        return 'RcConf<%s>' % self._conf.__repr__()
 

	
 
    def make_config(self, config):
 
        """
 
        Saves given config as a JSON dump in the _conf_name location
 

	
 
        :param config:
 
        """
 
        update = False
 
        if os.path.exists(self._conf_name):
 
            update = True
 
        with open(self._conf_name, 'wb') as f:
 
            json.dump(config, f, indent=4)
 

	
 
        if update:
 
            sys.stdout.write('Updated config in %s\n' % self._conf_name)
 
        else:
 
            sys.stdout.write('Created new config in %s\n' % self._conf_name)
 

	
 
    def update_config(self, new_config):
 
        """
 
        Reads the JSON config updates it's values with new_config and
 
        saves it back as JSON dump
 

	
 
        :param new_config:
 
        """
 
        config = {}
 
        try:
 
            with open(self._conf_name, 'rb') as conf:
 
                config = json.load(conf)
 
        except IOError, e:
 
            sys.stderr.write(str(e) + '\n')
 

	
 
        config.update(new_config)
 
        self.make_config(config)
 

	
 
    def load_config(self):
 
        """
 
        Loads config from file and returns loaded JSON object
 
        """
 
        try:
 
            with open(self._conf_name, 'rb') as conf:
 
                return  json.load(conf)
 
        except IOError, e:
 
            #sys.stderr.write(str(e) + '\n')
 
            pass
kallithea/bin/kallithea_api.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.api
 
~~~~~~~~~~~~~~~~~
 

	
 
Api CLI client for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Jun 3, 2012
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import with_statement
 
import sys
 
import argparse
 

	
 
from kallithea.bin.base import json, api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY
 

	
 

	
 
def argparser(argv):
 
    usage = (
 
      "kallithea-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
 
      "[--config=CONFIG] [--save-config] "
 
      "METHOD <key:val> <key2:val> ...\n"
 
      "Create config file: kallithea-api --apikey=<key> --apihost=http://your.kallithea.server --save-config"
 
    )
 

	
 
    parser = argparse.ArgumentParser(description='RhodeCode API cli',
 
                                     usage=usage)
 

	
 
    ## config
 
    group = parser.add_argument_group('config')
 
    group.add_argument('--apikey', help='api access key')
 
    group.add_argument('--apihost', help='api host')
 
    group.add_argument('--config', help='config file')
 
    group.add_argument('--save-config', action='store_true', help='save the given config into a file')
 

	
 
    group = parser.add_argument_group('API')
 
    group.add_argument('method', metavar='METHOD', nargs='?', type=str, default=None,
 
            help='API method name to call followed by key:value attributes',
 
    )
 
    group.add_argument('--format', dest='format', type=str,
 
            help='output format default: `%s` can '
 
                 'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON),
 
            default=FORMAT_PRETTY
 
    )
 
    args, other = parser.parse_known_args()
 
    return parser, args, other
 

	
 

	
 
def main(argv=None):
 
    """
 
    Main execution function for cli
 

	
 
    :param argv:
 
    """
 
    if argv is None:
 
        argv = sys.argv
 

	
 
    conf = None
 
    parser, args, other = argparser(argv)
 

	
 
    api_credentials_given = (args.apikey and args.apihost)
 
    if args.save_config:
 
        if not api_credentials_given:
 
            raise parser.error('--save-config requires --apikey and --apihost')
 
        conf = RcConf(config_location=args.config,
 
                      autocreate=True, config={'apikey': args.apikey,
 
                                               'apihost': args.apihost})
 
        sys.exit()
 

	
 
    if not conf:
 
        conf = RcConf(config_location=args.config, autoload=True)
 
        if not conf:
 
            if not api_credentials_given:
 
                parser.error('Could not find config file and missing '
 
                             '--apikey or --apihost in params')
 

	
 
    apikey = args.apikey or conf['apikey']
 
    apihost = args.apihost or conf['apihost']
 
    method = args.method
 

	
 
    # if we don't have method here it's an error
 
    if not method:
 
        parser.error('Please specify method name')
 

	
 
    try:
 
        margs = dict(map(lambda s: s.split(':', 1), other))
 
    except Exception:
 
        sys.stderr.write('Error parsing arguments \n')
 
        sys.exit()
 
    if args.format == FORMAT_PRETTY:
 
        print 'Calling method %s => %s' % (method, apihost)
 

	
 
    json_resp = api_call(apikey, apihost, method, **margs)
 
    error_prefix = ''
 
    if json_resp['error']:
 
        error_prefix = 'ERROR:'
 
        json_data = json_resp['error']
 
    else:
 
        json_data = json_resp['result']
 
    if args.format == FORMAT_JSON:
 
        print json.dumps(json_data)
 
    elif args.format == FORMAT_PRETTY:
 
        print 'Server response \n%s%s' % (
 
            error_prefix, json.dumps(json_data, indent=4, sort_keys=True)
 
        )
 
    return 0
 

	
 
if __name__ == '__main__':
 
    sys.exit(main(sys.argv))
kallithea/bin/kallithea_backup.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.backup_manager
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Repositories backup manager, it allows to backups all
 
repositories and send it to backup server using RSA key via ssh.
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Feb 28, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import os
 
import sys
 

	
 
import logging
 
import tarfile
 
import datetime
 
import subprocess
 

	
 
logging.basicConfig(level=logging.DEBUG,
 
                    format="%(asctime)s %(levelname)-5.5s %(message)s")
 

	
 

	
 
class BackupManager(object):
 
    def __init__(self, repos_location, rsa_key, backup_server):
 
        today = datetime.datetime.now().weekday() + 1
 
        self.backup_file_name = "repos.%s.tar.gz" % today
 

	
 
        self.id_rsa_path = self.get_id_rsa(rsa_key)
 
        self.repos_path = self.get_repos_path(repos_location)
 
        self.backup_server = backup_server
 

	
 
        self.backup_file_path = '/tmp'
 

	
 
        logging.info('starting backup for %s', self.repos_path)
 
        logging.info('backup target %s', self.backup_file_path)
 

	
 
    def get_id_rsa(self, rsa_key):
 
        if not os.path.isfile(rsa_key):
 
            logging.error('Could not load id_rsa key file in %s', rsa_key)
 
            sys.exit()
 
        return rsa_key
 

	
 
    def get_repos_path(self, path):
 
        if not os.path.isdir(path):
 
            logging.error('Wrong location for repositories in %s', path)
 
            sys.exit()
 
        return path
 

	
 
    def backup_repos(self):
 
        bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
 
        tar = tarfile.open(bckp_file, "w:gz")
 

	
 
        for dir_name in os.listdir(self.repos_path):
 
            logging.info('backing up %s', dir_name)
 
            tar.add(os.path.join(self.repos_path, dir_name), dir_name)
 
        tar.close()
 
        logging.info('finished backup of mercurial repositories')
 

	
 
    def transfer_files(self):
 
        params = {
 
                  'id_rsa_key': self.id_rsa_path,
 
                  'backup_file': os.path.join(self.backup_file_path,
 
                                             self.backup_file_name),
 
                  'backup_server': self.backup_server
 
                  }
 
        cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
 
               '%(backup_file)s' % params,
 
               '%(backup_server)s' % params]
 

	
 
        subprocess.call(cmd)
 
        logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
 

	
 
    def rm_file(self):
 
        logging.info('Removing file %s', self.backup_file_name)
 
        os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
 

	
 
if __name__ == "__main__":
 

	
 
    repo_location = '/home/repo_path'
 
    backup_server = 'root@192.168.1.100:/backups/mercurial'
 
    rsa_key = '/home/id_rsa'
 

	
 
    B_MANAGER = BackupManager(repo_location, rsa_key, backup_server)
 
    B_MANAGER.backup_repos()
 
    B_MANAGER.transfer_files()
 
    B_MANAGER.rm_file()
kallithea/bin/kallithea_config.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.kallithea_config
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
configuration generator for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Jun 18, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
from __future__ import with_statement
 
import os
 
import sys
 
import uuid
 
import argparse
 
from mako.template import Template
 
TMPL = 'template.ini.mako'
 
here = os.path.dirname(os.path.abspath(__file__))
 

	
 
def argparser(argv):
 
    usage = (
 
      "kallithea-config [-h] [--filename=FILENAME] [--template=TEMPLATE] \n"
 
      "VARS optional specify extra template variable that will be available in "
 
      "template. Use comma separated key=val format eg.\n"
 
      "key1=val1,port=5000,host=127.0.0.1,elements='a\,b\,c'\n"
 
    )
 

	
 
    parser = argparse.ArgumentParser(
 
        description='RhodeCode CONFIG generator with variable replacement',
 
        usage=usage
 
    )
 

	
 
    ## config
 
    group = parser.add_argument_group('CONFIG')
 
    group.add_argument('--filename', help='Output ini filename.')
 
    group.add_argument('--template', help='Mako template file to use instead of '
 
                                          'the default builtin template')
 
    group.add_argument('--raw', help='Store given mako template as raw without '
 
                                     'parsing. Use this to create custom template '
 
                                     'initially', action='store_true')
 
    group.add_argument('--show-defaults', help='Show all default variables for '
 
                                               'builtin template', action='store_true')
 
    args, other = parser.parse_known_args()
 
    return parser, args, other
 

	
 

	
 
def _escape_split(text, sep):
 
    """
 
    Allows for escaping of the separator: e.g. arg='foo\, bar'
 

	
 
    It should be noted that the way bash et. al. do command line parsing, those
 
    single quotes are required. a shameless ripoff from fabric project.
 

	
 
    """
 
    escaped_sep = r'\%s' % sep
 

	
 
    if escaped_sep not in text:
 
        return text.split(sep)
 

	
 
    before, _, after = text.partition(escaped_sep)
 
    startlist = before.split(sep)  # a regular split is fine here
 
    unfinished = startlist[-1]
 
    startlist = startlist[:-1]
 

	
 
    # recurse because there may be more escaped separators
 
    endlist = _escape_split(after, sep)
 

	
 
    # finish building the escaped value. we use endlist[0] becaue the first
 
    # part of the string sent in recursion is the rest of the escaped value.
 
    unfinished += sep + endlist[0]
 

	
 
    return startlist + [unfinished] + endlist[1:]  # put together all the parts
 

	
 
def _run(argv):
 
    parser, args, other = argparser(argv)
 
    if not len(sys.argv) > 1:
 
        print parser.print_help()
 
        sys.exit(0)
 
    # defaults that can be overwritten by arguments
 
    tmpl_stored_args = {
 
        'http_server': 'waitress',
 
        'lang': 'en',
 
        'database_engine': 'sqlite',
 
        'host': '127.0.0.1',
 
        'port': 5000,
 
        'error_aggregation_service': None,
 
    }
 
    if other:
 
        # parse arguments, we assume only first is correct
 
        kwargs = {}
 
        for el in _escape_split(other[0], ','):
 
            kv = _escape_split(el, '=')
 
            if len(kv) == 2:
 
                k, v = kv
 
                kwargs[k] = v
 
        # update our template stored args
 
        tmpl_stored_args.update(kwargs)
 

	
 
    # use default that cannot be replaced
 
    tmpl_stored_args.update({
 
        'uuid': lambda: uuid.uuid4().hex,
 
        'here': os.path.abspath(os.curdir),
 
    })
 
    if args.show_defaults:
 
        for k,v in tmpl_stored_args.iteritems():
 
            print '%s=%s' % (k, v)
 
        sys.exit(0)
 
    try:
 
        # built in template
 
        tmpl_file = os.path.join(here, TMPL)
 
        if args.template:
 
            tmpl_file = args.template
 

	
 
        with open(tmpl_file, 'rb') as f:
 
            tmpl_data = f.read()
 
            if args.raw:
 
                tmpl = tmpl_data
 
            else:
 
                tmpl = Template(tmpl_data).render(**tmpl_stored_args)
 
        with open(args.filename, 'wb') as f:
 
            f.write(tmpl)
 
        print 'Wrote new config file in %s' % (os.path.abspath(args.filename))
 

	
 
    except Exception:
 
        from mako import exceptions
 
        print exceptions.text_error_template().render()
 

	
 
def main(argv=None):
 
    """
 
    Main execution function for cli
 

	
 
    :param argv:
 
    """
 
    if argv is None:
 
        argv = sys.argv
 

	
 
    try:
 
        return _run(argv)
 
    except Exception:
 
        raise
 

	
 

	
 
if __name__ == '__main__':
 
    sys.exit(main(sys.argv))
kallithea/bin/kallithea_gist.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.kallithea_gist
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Gist CLI client for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: May 9, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import with_statement
 
import os
 
import sys
 
import stat
 
import argparse
 
import fileinput
 

	
 
from kallithea.bin.base import json, api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY
 

	
 

	
 
def argparser(argv):
 
    usage = (
 
      "kallithea-gist [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
 
      "[--config=CONFIG] [--save-config] [GIST OPTIONS] "
 
      "[filename or stdin use - for terminal stdin ]\n"
 
      "Create config file: kallithea-gist --apikey=<key> --apihost=http://your.kallithea.server --save-config"
 
    )
 

	
 
    parser = argparse.ArgumentParser(description='RhodeCode Gist cli',
 
                                     usage=usage)
 

	
 
    ## config
 
    group = parser.add_argument_group('config')
 
    group.add_argument('--apikey', help='api access key')
 
    group.add_argument('--apihost', help='api host')
 
    group.add_argument('--config', help='config file path DEFAULT: ~/.config/kallithea')
 
    group.add_argument('--save-config', action='store_true',
 
                       help='save the given config into a file')
 

	
 
    group = parser.add_argument_group('GIST')
 
    group.add_argument('-p', '--private', action='store_true',
 
                       help='create private Gist')
 
    group.add_argument('-f', '--filename',
 
                       help='set uploaded gist filename, '
 
                            'also defines syntax highlighting')
 
    group.add_argument('-d', '--description', help='Gist description')
 
    group.add_argument('-l', '--lifetime', metavar='MINUTES',
 
                       help='gist lifetime in minutes, -1 (DEFAULT) is forever')
 
    group.add_argument('--format', dest='format', type=str,
 
                       help='output format DEFAULT: `%s` can '
 
                       'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON),
 
            default=FORMAT_PRETTY
 
    )
 
    args, other = parser.parse_known_args()
 
    return parser, args, other
 

	
 

	
 
def _run(argv):
 
    conf = None
 
    parser, args, other = argparser(argv)
 

	
 
    api_credentials_given = (args.apikey and args.apihost)
 
    if args.save_config:
 
        if not api_credentials_given:
 
            raise parser.error('--save-config requires --apikey and --apihost')
 
        conf = RcConf(config_location=args.config,
 
                      autocreate=True, config={'apikey': args.apikey,
 
                                               'apihost': args.apihost})
 
        sys.exit()
 

	
 
    if not conf:
 
        conf = RcConf(config_location=args.config, autoload=True)
 
        if not conf:
 
            if not api_credentials_given:
 
                parser.error('Could not find config file and missing '
 
                             '--apikey or --apihost in params')
 

	
 
    apikey = args.apikey or conf['apikey']
 
    host = args.apihost or conf['apihost']
 
    DEFAULT_FILENAME = 'gistfile1.txt'
 
    if other:
 
        # skip multifiles for now
 
        filename = other[0]
 
        if filename == '-':
 
            filename = DEFAULT_FILENAME
 
            gist_content = ''
 
            for line in fileinput.input('-'):
 
                gist_content += line
 
        else:
 
            with open(filename, 'rb') as f:
 
                gist_content = f.read()
 

	
 
    else:
 
        filename = DEFAULT_FILENAME
 
        gist_content = None
 
        # little bit hacky but cross platform check where the
 
        # stdin comes from we skip the terminal case it can be handled by '-'
 
        mode = os.fstat(0).st_mode
 
        if stat.S_ISFIFO(mode):
 
            # "stdin is piped"
 
            gist_content = sys.stdin.read()
 
        elif stat.S_ISREG(mode):
 
            # "stdin is redirected"
 
            gist_content = sys.stdin.read()
 
        else:
 
            # "stdin is terminal"
 
            pass
 

	
 
    # make sure we don't upload binary stuff
 
    if gist_content and '\0' in gist_content:
 
        raise Exception('Error: binary files upload is not possible')
 

	
 
    filename = os.path.basename(args.filename or filename)
 
    if gist_content:
 
        files = {
 
            filename: {
 
                'content': gist_content,
 
                'lexer': None
 
            }
 
        }
 

	
 
        margs = dict(
 
            lifetime=args.lifetime,
 
            description=args.description,
 
            gist_type='private' if args.private else 'public',
 
            files=files
 
        )
 

	
 
        json_data = api_call(apikey, host, 'create_gist', **margs)['result']
 
        if args.format == FORMAT_JSON:
 
            print json.dumps(json_data)
 
        elif args.format == FORMAT_PRETTY:
 
            print json_data
 
            print 'Created %s gist %s' % (json_data['gist']['type'],
 
                                          json_data['gist']['url'])
 
    return 0
 

	
 

	
 
def main(argv=None):
 
    """
 
    Main execution function for cli
 

	
 
    :param argv:
 
    """
 
    if argv is None:
 
        argv = sys.argv
 

	
 
    try:
 
        return _run(argv)
 
    except Exception, e:
 
        print e
 
        return 1
 

	
 

	
 
if __name__ == '__main__':
 
    sys.exit(main(sys.argv))
kallithea/bin/ldap_sync.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.bin.__init__
 
~~~~~~~~~~~~~~~~~~~~~~
 

	
 
LDAP sync script
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Mar 06, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import ldap
 
import urllib2
 
import uuid
 

	
 
try:
 
    from kallithea.lib.compat import json
 
except ImportError:
 
    try:
 
        import simplejson as json
 
    except ImportError:
 
        import json
 

	
 
from ConfigParser import ConfigParser
 

	
 
config = ConfigParser()
 
config.read('ldap_sync.conf')
 

	
 

	
 
class InvalidResponseIDError(Exception):
 
    """ Request and response don't have the same UUID. """
 

	
 

	
 
class ResponseError(Exception):
 
    """ Response has an error, something went wrong with request execution. """
 

	
 

	
 
class UserAlreadyInGroupError(Exception):
 
    """ User is already a member of the target group. """
 

	
 

	
 
class UserNotInGroupError(Exception):
 
    """ User is not a member of the target group. """
 

	
 

	
 
class API(object):
 

	
 
    def __init__(self, url, key):
 
        self.url = url
 
        self.key = key
 

	
 
    def get_api_data(self, uid, method, args):
 
        """Prepare dict for API post."""
 
        return {
 
            "id": uid,
 
            "api_key": self.key,
 
            "method": method,
 
            "args": args
 
        }
 

	
 
    def post(self, method, args):
 
        """Send a generic API post to Rhodecode.
 

	
 
        This will generate the UUID for validation check after the
 
        response is returned. Handle errors and get the result back.
 
        """
 
        uid = str(uuid.uuid1())
 
        data = self.get_api_data(uid, method, args)
 

	
 
        data = json.dumps(data)
 
        headers = {'content-type': 'text/plain'}
 
        req = urllib2.Request(self.url, data, headers)
 

	
 
        response = urllib2.urlopen(req)
 
        response = json.load(response)
 

	
 
        if uid != response["id"]:
 
            raise InvalidResponseIDError("UUID does not match.")
 

	
 
        if response["error"] is not None:
 
            raise ResponseError(response["error"])
 

	
 
        return response["result"]
 

	
 
    def create_group(self, name, active=True):
 
        """Create the Rhodecode user group."""
 
        args = {
 
            "group_name": name,
 
            "active": str(active)
 
        }
 
        self.post("create_user_group", args)
 

	
 
    def add_membership(self, group, username):
 
        """Add specific user to a group."""
 
        args = {
 
            "usersgroupid": group,
 
            "userid": username
 
        }
 
        result = self.post("add_user_to_user_group", args)
 
        if not result["success"]:
 
            raise UserAlreadyInGroupError("User %s already in group %s." %
 
                                          (username, group))
 

	
 
    def remove_membership(self, group, username):
 
        """Remove specific user from a group."""
 
        args = {
 
            "usersgroupid": group,
 
            "userid": username
 
        }
 
        result = self.post("remove_user_from_user_group", args)
 
        if not result["success"]:
 
            raise UserNotInGroupError("User %s not in group %s." %
 
                                      (username, group))
 

	
 
    def get_group_members(self, name):
 
        """Get the list of member usernames from a user group."""
 
        args = {"usersgroupid": name}
 
        members = self.post("get_user_group", args)['members']
 
        member_list = []
 
        for member in members:
 
            member_list.append(member["username"])
 
        return member_list
 

	
 
    def get_group(self, name):
 
        """Return group info."""
 
        args = {"usersgroupid": name}
 
        return self.post("get_user_group", args)
 

	
 
    def get_user(self, username):
 
        """Return user info."""
 
        args = {"userid": username}
 
        return self.post("get_user", args)
 

	
 

	
 
class LdapClient(object):
 

	
 
    def __init__(self, uri, user, key, base_dn):
 
        self.client = ldap.initialize(uri, trace_level=0)
 
        self.client.set_option(ldap.OPT_REFERRALS, 0)
 
        self.client.simple_bind(user, key)
 
        self.base_dn = base_dn
 

	
 
    def __del__(self):
 
        self.client.unbind()
 

	
 
    def get_groups(self):
 
        """Get all the groups in form of dict {group_name: group_info,...}."""
 
        searchFilter = "objectClass=groupOfUniqueNames"
 
        result = self.client.search_s(self.base_dn, ldap.SCOPE_SUBTREE,
 
                                      searchFilter)
 

	
 
        groups = {}
 
        for group in result:
 
            groups[group[1]['cn'][0]] = group[1]
 

	
 
        return groups
 

	
 
    def get_group_users(self, groups, group):
 
        """Returns all the users belonging to a single group.
 

	
 
        Based on the list of groups and memberships, returns all the
 
        users belonging to a single group, searching recursively.
 
        """
 
        users = []
 
        for member in groups[group]["uniqueMember"]:
 
            member = self.parse_member_string(member)
 
            if member[0] == "uid":
 
                users.append(member[1])
 
            elif member[0] == "cn":
 
                users += self.get_group_users(groups, member[1])
 

	
 
        return users
 

	
 
    def parse_member_string(self, member):
 
        """Parses the member string and returns a touple of type and name.
 

	
 
        Unique member can be either user or group. Users will have 'uid' as
 
        prefix while groups will have 'cn'.
 
        """
 
        member = member.split(",")[0]
 
        return member.split('=')
 

	
 

	
 
class LdapSync(object):
 

	
 
    def __init__(self):
 
        self.ldap_client = LdapClient(config.get("default", "ldap_uri"),
 
                                      config.get("default", "ldap_user"),
 
                                      config.get("default", "ldap_key"),
 
                                      config.get("default", "base_dn"))
 
        self.rhodocode_api = API(config.get("default", "api_url"),
 
                                          config.get("default", "api_key"))
 

	
 
    def update_groups_from_ldap(self):
 
        """Add all the groups from LDAP to Rhodecode."""
 
        added = existing = 0
 
        groups = self.ldap_client.get_groups()
 
        for group in groups:
 
            try:
 
                self.kallithea_api.create_repo_group(group)
 
                added += 1
 
            except Exception:
 
                existing += 1
 

	
 
        return added, existing
 

	
 
    def update_memberships_from_ldap(self, group):
 
        """Update memberships based on the LDAP groups."""
 
        groups = self.ldap_client.get_groups()
 
        group_users = self.ldap_client.get_group_users(groups, group)
 

	
 
        # Delete memberships first from each group which are not part
 
        # of the group any more.
 
        members = self.kallithea_api.get_group_members(group)
 
        for member in members:
 
            if member not in group_users:
 
                try:
 
                    self.rhodocode_api.remove_membership(group,
 
                                                         member)
 
                except UserNotInGroupError:
 
                    pass
 

	
 
        # Add memberships.
 
        for member in group_users:
 
            try:
 
                self.kallithea_api.add_membership(group, member)
 
            except UserAlreadyInGroupError:
 
                # TODO: handle somehow maybe..
 
                pass
 

	
 

	
 
if __name__ == '__main__':
 
    sync = LdapSync()
 
    print sync.update_groups_from_ldap()
 

	
 
    for gr in sync.ldap_client.get_groups():
 
        # TODO: exception when user does not exist during add membership...
 
        # How should we handle this.. Either sync users as well at this step,
 
        # or just ignore those who don't exist. If we want the second case,
 
        # we need to find a way to recognize the right exception (we always get
 
        # ResponseError with no error code so maybe by return msg (?)
 
        sync.update_memberships_from_ldap(gr)
kallithea/config/conf.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.config.conf
 
~~~~~~~~~~~~~~~~~~~~~
 

	
 
Various config settings for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Mar 7, 2012
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 
from kallithea import EXTENSIONS
 

	
 
from kallithea.lib.utils2 import __get_lem
 

	
 

	
 
# language map is also used by whoosh indexer, which for those specified
 
# extensions will index it's content
 
LANGUAGES_EXTENSIONS_MAP = __get_lem()
 

	
 
#==============================================================================
 
# WHOOSH INDEX EXTENSIONS
 
#==============================================================================
 
# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH
 
INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
 

	
 
# list of readme files to search in file tree and display in summary
 
# attached weights defines the search  order lower is first
 
ALL_READMES = [
 
    ('readme', 0), ('README', 0), ('Readme', 0),
 
    ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
 
    ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
 
    ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
 
    ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
 
]
 

	
 
# extension together with weights to search lower is first
 
RST_EXTS = [
 
    ('', 0), ('.rst', 1), ('.rest', 1),
 
    ('.RST', 2), ('.REST', 2),
 
    ('.txt', 3), ('.TXT', 3)
 
]
 

	
 
MARKDOWN_EXTS = [
 
    ('.md', 1), ('.MD', 1),
 
    ('.mkdn', 2), ('.MKDN', 2),
 
    ('.mdown', 3), ('.MDOWN', 3),
 
    ('.markdown', 4), ('.MARKDOWN', 4)
 
]
 

	
 
PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
 

	
 
ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
 

	
 
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
 

	
 
DATE_FORMAT = "%Y-%m-%d"
kallithea/controllers/admin/admin.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.admin
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Controller for Admin panel of Rhodecode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 7, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
import logging
 

	
 
from pylons import request, tmpl_context as c, url
 
from sqlalchemy.orm import joinedload
 
from whoosh.qparser.default import QueryParser
 
from whoosh.qparser.dateparse import DateParserPlugin
 
from whoosh import query
 
from sqlalchemy.sql.expression import or_, and_, func
 

	
 
from kallithea.model.db import UserLog, User
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.utils2 import safe_int, remove_prefix, remove_suffix
 
from kallithea.lib.indexers import JOURNAL_SCHEMA
 
from kallithea.lib.helpers import Page
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _journal_filter(user_log, search_term):
 
    """
 
    Filters sqlalchemy user_log based on search_term with whoosh Query language
 
    http://packages.python.org/Whoosh/querylang.html
 

	
 
    :param user_log:
 
    :param search_term:
 
    """
 
    log.debug('Initial search term: %r' % search_term)
 
    qry = None
 
    if search_term:
 
        qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
 
        qp.add_plugin(DateParserPlugin())
 
        qry = qp.parse(unicode(search_term))
 
        log.debug('Filtering using parsed query %r' % qry)
 

	
 
    def wildcard_handler(col, wc_term):
 
        if wc_term.startswith('*') and not wc_term.endswith('*'):
 
            #postfix == endswith
 
            wc_term = remove_prefix(wc_term, prefix='*')
 
            return func.lower(col).endswith(wc_term)
 
        elif wc_term.startswith('*') and wc_term.endswith('*'):
 
            #wildcard == ilike
 
            wc_term = remove_prefix(wc_term, prefix='*')
 
            wc_term = remove_suffix(wc_term, suffix='*')
 
            return func.lower(col).contains(wc_term)
 

	
 
    def get_filterion(field, val, term):
 

	
 
        if field == 'repository':
 
            field = getattr(UserLog, 'repository_name')
 
        elif field == 'ip':
 
            field = getattr(UserLog, 'user_ip')
 
        elif field == 'date':
 
            field = getattr(UserLog, 'action_date')
 
        elif field == 'username':
 
            field = getattr(UserLog, 'username')
 
        else:
 
            field = getattr(UserLog, field)
 
        log.debug('filter field: %s val=>%s' % (field, val))
 

	
 
        #sql filtering
 
        if isinstance(term, query.Wildcard):
 
            return wildcard_handler(field, val)
 
        elif isinstance(term, query.Prefix):
 
            return func.lower(field).startswith(func.lower(val))
 
        elif isinstance(term, query.DateRange):
 
            return and_(field >= val[0], field <= val[1])
 
        return func.lower(field) == func.lower(val)
 

	
 
    if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
 
                        query.DateRange)):
 
        if not isinstance(qry, query.And):
 
            qry = [qry]
 
        for term in qry:
 
            field = term.fieldname
 
            val = (term.text if not isinstance(term, query.DateRange)
 
                   else [term.startdate, term.enddate])
 
            user_log = user_log.filter(get_filterion(field, val, term))
 
    elif isinstance(qry, query.Or):
 
        filters = []
 
        for term in qry:
 
            field = term.fieldname
 
            val = (term.text if not isinstance(term, query.DateRange)
 
                   else [term.startdate, term.enddate])
 
            filters.append(get_filterion(field, val, term))
 
        user_log = user_log.filter(or_(*filters))
 

	
 
    return user_log
 

	
 

	
 
class AdminController(BaseController):
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(AdminController, self).__before__()
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def index(self):
 
        users_log = UserLog.query()\
 
                .options(joinedload(UserLog.user))\
 
                .options(joinedload(UserLog.repository))
 

	
 
        #FILTERING
 
        c.search_term = request.GET.get('filter')
 
        try:
 
            users_log = _journal_filter(users_log, c.search_term)
 
        except Exception:
 
            # we want this to crash for now
 
            raise
 

	
 
        users_log = users_log.order_by(UserLog.action_date.desc())
 

	
 
        p = safe_int(request.GET.get('page', 1), 1)
 

	
 
        def url_generator(**kw):
 
            return url.current(filter=c.search_term, **kw)
 

	
 
        c.users_log = Page(users_log, page=p, items_per_page=10, url=url_generator)
 
        c.log_data = render('admin/admin_log.html')
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return c.log_data
 
        return render('admin/admin.html')
kallithea/controllers/admin/auth_settings.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.auth_settings
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
pluggable authentication controller for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Nov 26, 2010
 
:author: akesterson
 
"""
 

	
 
import pprint
 
import logging
 
import formencode.htmlfill
 
import traceback
 

	
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from sqlalchemy.exc import DatabaseError
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.compat import json, formatted_json
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
 
from kallithea.lib import auth_modules
 
from kallithea.model.forms import AuthSettingsForm
 
from kallithea.model.db import Setting
 
from kallithea.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class AuthSettingsController(BaseController):
 

	
 
    @LoginRequired()
 
    @HasPermissionAllDecorator('hg.admin')
 
    def __before__(self):
 
        super(AuthSettingsController, self).__before__()
 

	
 
    def __load_defaults(self):
 
        c.available_plugins = [
 
            'kallithea.lib.auth_modules.auth_internal',
 
            'kallithea.lib.auth_modules.auth_container',
 
            'kallithea.lib.auth_modules.auth_ldap',
 
            'kallithea.lib.auth_modules.auth_crowd',
 
        ]
 
        c.enabled_plugins = Setting.get_auth_plugins()
 

	
 
    def index(self, defaults=None, errors=None, prefix_error=False):
 
        self.__load_defaults()
 
        _defaults = {}
 
        # default plugins loaded
 
        formglobals = {
 
            "auth_plugins": ["kallithea.lib.auth_modules.auth_internal"]
 
        }
 
        formglobals.update(Setting.get_auth_settings())
 
        formglobals["plugin_settings"] = {}
 
        formglobals["auth_plugins_shortnames"] = {}
 
        _defaults["auth_plugins"] = formglobals["auth_plugins"]
 

	
 
        for module in formglobals["auth_plugins"]:
 
            plugin = auth_modules.loadplugin(module)
 
            plugin_name = plugin.name
 
            formglobals["auth_plugins_shortnames"][module] = plugin_name
 
            formglobals["plugin_settings"][module] = plugin.plugin_settings()
 
            for v in formglobals["plugin_settings"][module]:
 
                fullname = ("auth_" + plugin_name + "_" + v["name"])
 
                if "default" in v:
 
                    _defaults[fullname] = v["default"]
 
                # Current values will be the default on the form, if there are any
 
                setting = Setting.get_by_name(fullname)
 
                if setting:
 
                    _defaults[fullname] = setting.app_settings_value
 
        # we want to show , seperated list of enabled plugins
 
        _defaults['auth_plugins'] = ','.join(_defaults['auth_plugins'])
 
        if defaults:
 
            _defaults.update(defaults)
 

	
 
        formglobals["defaults"] = _defaults
 
        # set template context variables
 
        for k, v in formglobals.iteritems():
 
            setattr(c, k, v)
 

	
 
        log.debug(pprint.pformat(formglobals, indent=4))
 
        log.debug(formatted_json(defaults))
 
        return formencode.htmlfill.render(
 
            render('admin/auth/auth_settings.html'),
 
            defaults=_defaults,
 
            errors=errors,
 
            prefix_error=prefix_error,
 
            encoding="UTF-8",
 
            force_defaults=True,
 
        )
 

	
 
    def auth_settings(self):
 
        """POST create and store auth settings"""
 
        self.__load_defaults()
 
        _form = AuthSettingsForm(c.enabled_plugins)()
 
        log.debug("POST Result: %s" % formatted_json(dict(request.POST)))
 

	
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            for k, v in form_result.items():
 
                if k == 'auth_plugins':
 
                    # we want to store it comma separated inside our settings
 
                    v = ','.join(v)
 
                log.debug("%s = %s" % (k, str(v)))
 
                setting = Setting.create_or_update(k, v)
 
                Session().add(setting)
 
            Session().commit()
 
            h.flash(_('Auth settings updated successfully'),
 
                       category='success')
 
        except formencode.Invalid, errors:
 
            log.error(traceback.format_exc())
 
            e = errors.error_dict or {}
 
            return self.index(
 
                defaults=errors.value,
 
                errors=e,
 
                prefix_error=False)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('error occurred during update of auth settings'),
 
                    category='error')
 

	
 
        return redirect(url('auth_home'))
kallithea/controllers/admin/defaults.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.defaults
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
default settings controller for Rhodecode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 27, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
import formencode
 
from formencode import htmlfill
 

	
 
from pylons import request, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
 
from kallithea.lib.base import BaseController, render
 
from kallithea.model.forms import DefaultsForm
 
from kallithea.model.meta import Session
 
from kallithea import BACKENDS
 
from kallithea.model.db import Setting
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class DefaultsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('default', 'defaults')
 

	
 
    @LoginRequired()
 
    @HasPermissionAllDecorator('hg.admin')
 
    def __before__(self):
 
        super(DefaultsController, self).__before__()
 

	
 
    def index(self, format='html'):
 
        """GET /defaults: All items in the collection"""
 
        # url('defaults')
 
        c.backends = BACKENDS.keys()
 
        defaults = Setting.get_default_repo_settings()
 

	
 
        return htmlfill.render(
 
            render('admin/defaults/defaults.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    def create(self):
 
        """POST /defaults: Create a new item"""
 
        # url('defaults')
 

	
 
    def new(self, format='html'):
 
        """GET /defaults/new: Form to create a new item"""
 
        # url('new_default')
 

	
 
    def update(self, id):
 
        """PUT /defaults/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('default', id=ID),
 
        #           method='put')
 
        # url('default', id=ID)
 

	
 
        _form = DefaultsForm()()
 

	
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            for k, v in form_result.iteritems():
 
                setting = Setting.create_or_update(k, v)
 
                Session().add(setting)
 
            Session().commit()
 
            h.flash(_('Default settings updated successfully'),
 
                    category='success')
 

	
 
        except formencode.Invalid, errors:
 
            defaults = errors.value
 

	
 
            return htmlfill.render(
 
                render('admin/defaults/defaults.html'),
 
                defaults=defaults,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during update of defaults'),
 
                    category='error')
 

	
 
        return redirect(url('defaults'))
 

	
 
    def delete(self, id):
 
        """DELETE /defaults/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('default', id=ID),
 
        #           method='delete')
 
        # url('default', id=ID)
 

	
 
    def show(self, id, format='html'):
 
        """GET /defaults/id: Show a specific item"""
 
        # url('default', id=ID)
 

	
 
    def edit(self, id, format='html'):
 
        """GET /defaults/id/edit: Form to edit an existing item"""
 
        # url('edit_default', id=ID)
kallithea/controllers/admin/gists.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.gist
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
gist controller for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: May 9, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import time
 
import logging
 
import traceback
 
import formencode
 
from formencode import htmlfill
 

	
 
from pylons import request, response, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from kallithea.model.forms import GistForm
 
from kallithea.model.gist import GistModel
 
from kallithea.model.meta import Session
 
from kallithea.model.db import Gist, User
 
from kallithea.lib import helpers as h
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.auth import LoginRequired, NotAnonymous
 
from kallithea.lib.utils import jsonify
 
from kallithea.lib.utils2 import safe_str, safe_int, time_to_datetime
 
from kallithea.lib.helpers import Page
 
from webob.exc import HTTPNotFound, HTTPForbidden
 
from sqlalchemy.sql.expression import or_
 
from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class GistsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    def __load_defaults(self, extra_values=None):
 
        c.lifetime_values = [
 
            (str(-1), _('forever')),
 
            (str(5), _('5 minutes')),
 
            (str(60), _('1 hour')),
 
            (str(60 * 24), _('1 day')),
 
            (str(60 * 24 * 30), _('1 month')),
 
        ]
 
        if extra_values:
 
            c.lifetime_values.append(extra_values)
 
        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 

	
 
    @LoginRequired()
 
    def index(self):
 
        """GET /admin/gists: All items in the collection"""
 
        # url('gists')
 
        not_default_user = c.authuser.username != User.DEFAULT_USER
 
        c.show_private = request.GET.get('private') and not_default_user
 
        c.show_public = request.GET.get('public') and not_default_user
 

	
 
        gists = Gist().query()\
 
            .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
 
            .order_by(Gist.created_on.desc())
 

	
 
        # MY private
 
        if c.show_private and not c.show_public:
 
            gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
 
                             .filter(Gist.gist_owner == c.authuser.user_id)
 
        # MY public
 
        elif c.show_public and not c.show_private:
 
            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
 
                             .filter(Gist.gist_owner == c.authuser.user_id)
 

	
 
        # MY public+private
 
        elif c.show_private and c.show_public:
 
            gists = gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
 
                                     Gist.gist_type == Gist.GIST_PRIVATE))\
 
                             .filter(Gist.gist_owner == c.authuser.user_id)
 

	
 
        # default show ALL public gists
 
        if not c.show_public and not c.show_private:
 
            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
 

	
 
        c.gists = gists
 
        p = safe_int(request.GET.get('page', 1), 1)
 
        c.gists_pager = Page(c.gists, page=p, items_per_page=10)
 
        return render('admin/gists/index.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def create(self):
 
        """POST /admin/gists: Create a new item"""
 
        # url('gists')
 
        self.__load_defaults()
 
        gist_form = GistForm([x[0] for x in c.lifetime_values])()
 
        try:
 
            form_result = gist_form.to_python(dict(request.POST))
 
            #TODO: multiple files support, from the form
 
            filename = form_result['filename'] or Gist.DEFAULT_FILENAME
 
            nodes = {
 
                filename: {
 
                    'content': form_result['content'],
 
                    'lexer': form_result['mimetype']  # None is autodetect
 
                }
 
            }
 
            _public = form_result['public']
 
            gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
 
            gist = GistModel().create(
 
                description=form_result['description'],
 
                owner=c.authuser.user_id,
 
                gist_mapping=nodes,
 
                gist_type=gist_type,
 
                lifetime=form_result['lifetime']
 
            )
 
            Session().commit()
 
            new_gist_id = gist.gist_access_id
 
        except formencode.Invalid, errors:
 
            defaults = errors.value
 

	
 
            return formencode.htmlfill.render(
 
                render('admin/gists/new.html'),
 
                defaults=defaults,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8"
 
            )
 

	
 
        except Exception, e:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during gist creation'), category='error')
 
            return redirect(url('new_gist'))
 
        return redirect(url('gist', gist_id=new_gist_id))
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def new(self, format='html'):
 
        """GET /admin/gists/new: Form to create a new item"""
 
        # url('new_gist')
 
        self.__load_defaults()
 
        return render('admin/gists/new.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def update(self, gist_id):
 
        """PUT /admin/gists/gist_id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('gist', gist_id=ID),
 
        #           method='put')
 
        # url('gist', gist_id=ID)
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def delete(self, gist_id):
 
        """DELETE /admin/gists/gist_id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('gist', gist_id=ID),
 
        #           method='delete')
 
        # url('gist', gist_id=ID)
 
        gist = GistModel().get_gist(gist_id)
 
        owner = gist.gist_owner == c.authuser.user_id
 
        if h.HasPermissionAny('hg.admin')() or owner:
 
            GistModel().delete(gist)
 
            Session().commit()
 
            h.flash(_('Deleted gist %s') % gist.gist_access_id, category='success')
 
        else:
 
            raise HTTPForbidden()
 

	
 
        return redirect(url('gists'))
 

	
 
    @LoginRequired()
 
    def show(self, gist_id, revision='tip', format='html', f_path=None):
 
        """GET /admin/gists/gist_id: Show a specific item"""
 
        # url('gist', gist_id=ID)
 
        c.gist = Gist.get_or_404(gist_id)
 

	
 
        #check if this gist is not expired
 
        if c.gist.gist_expires != -1:
 
            if time.time() > c.gist.gist_expires:
 
                log.error('Gist expired at %s' %
 
                          (time_to_datetime(c.gist.gist_expires)))
 
                raise HTTPNotFound()
 
        try:
 
            c.file_changeset, c.files = GistModel().get_gist_files(gist_id,
 
                                                            revision=revision)
 
        except VCSError:
 
            log.error(traceback.format_exc())
 
            raise HTTPNotFound()
 
        if format == 'raw':
 
            content = '\n\n'.join([f.content for f in c.files if (f_path is None or f.path == f_path)])
 
            response.content_type = 'text/plain'
 
            return content
 
        return render('admin/gists/show.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def edit(self, gist_id, format='html'):
 
        """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
 
        # url('edit_gist', gist_id=ID)
 
        c.gist = Gist.get_or_404(gist_id)
 

	
 
        #check if this gist is not expired
 
        if c.gist.gist_expires != -1:
 
            if time.time() > c.gist.gist_expires:
 
                log.error('Gist expired at %s' %
 
                          (time_to_datetime(c.gist.gist_expires)))
 
                raise HTTPNotFound()
 
        try:
 
            c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
 
        except VCSError:
 
            log.error(traceback.format_exc())
 
            raise HTTPNotFound()
 

	
 
        self.__load_defaults(extra_values=('0', _('unmodified')))
 
        rendered = render('admin/gists/edit.html')
 

	
 
        if request.POST:
 
            rpost = request.POST
 
            nodes = {}
 
            for org_filename, filename, mimetype, content in zip(
 
                                                    rpost.getall('org_files'),
 
                                                    rpost.getall('files'),
 
                                                    rpost.getall('mimetypes'),
 
                                                    rpost.getall('contents')):
 

	
 
                nodes[org_filename] = {
 
                    'org_filename': org_filename,
 
                    'filename': filename,
 
                    'content': content,
 
                    'lexer': mimetype,
 
                }
 
            try:
 
                GistModel().update(
 
                    gist=c.gist,
 
                    description=rpost['description'],
 
                    owner=c.gist.owner,
 
                    gist_mapping=nodes,
 
                    gist_type=c.gist.gist_type,
 
                    lifetime=rpost['lifetime']
 
                )
 

	
 
                Session().commit()
 
                h.flash(_('Successfully updated gist content'), category='success')
 
            except NodeNotChangedError:
 
                # raised if nothing was changed in repo itself. We anyway then
 
                # store only DB stuff for gist
 
                Session().commit()
 
                h.flash(_('Successfully updated gist data'), category='success')
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during update of gist %s') % gist_id,
 
                        category='error')
 

	
 
            return redirect(url('gist', gist_id=gist_id))
 

	
 
        return rendered
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @jsonify
 
    def check_revision(self, gist_id):
 
        c.gist = Gist.get_or_404(gist_id)
 
        last_rev = c.gist.scm_instance.get_changeset()
 
        success = True
 
        revision = request.POST.get('revision')
 

	
 
        ##TODO: maybe move this to model ?
 
        if revision != last_rev.raw_id:
 
            log.error('Last revision %s is different then submited %s'
 
                      % (revision, last_rev))
 
            # our gist has newer version than we
 
            success = False
 

	
 
        return {'success': success}
kallithea/controllers/admin/my_account.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.my_account
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
my account controller for rhodecode admin
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: August 20, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import time
 
import logging
 
import traceback
 
import formencode
 

	
 
from sqlalchemy import func, or_
 
from formencode import htmlfill
 
from pylons import request, tmpl_context as c, url
 
from pylons.controllers.util import redirect
 
from pylons.i18n.translation import _
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.utils2 import generate_api_key, safe_int
 
from kallithea.lib.compat import json
 
from kallithea.model.db import Repository, PullRequest, PullRequestReviewers, \
 
    UserEmailMap, UserApiKeys, User, UserFollowing
 
from kallithea.model.forms import UserForm, PasswordChangeForm
 
from kallithea.model.user import UserModel
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.api_key import ApiKeyModel
 
from kallithea.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class MyAccountController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('setting', 'settings', controller='admin/settings',
 
    #         path_prefix='/admin', name_prefix='admin_')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def __before__(self):
 
        super(MyAccountController, self).__before__()
 

	
 
    def __load_data(self):
 
        c.user = User.get(self.authuser.user_id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user since it's"
 
                      " crucial for entire application"), category='warning')
 
            return redirect(url('users'))
 

	
 
    def _load_my_repos_data(self, watched=False):
 
        if watched:
 
            admin = False
 
            repos_list = [x.follows_repository for x in
 
                          Session().query(UserFollowing).filter(
 
                              UserFollowing.user_id ==
 
                              self.authuser.user_id).all()]
 
        else:
 
            admin = True
 
            repos_list = Session().query(Repository)\
 
                         .filter(Repository.user_id ==
 
                                 self.authuser.user_id)\
 
                         .order_by(func.lower(Repository.repo_name)).all()
 

	
 
        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
 
                                                   admin=admin)
 
        #json used to render the grid
 
        return json.dumps(repos_data)
 

	
 
    def my_account(self):
 
        """
 
        GET /_admin/my_account Displays info about my account
 
        """
 
        # url('my_account')
 
        c.active = 'profile'
 
        self.__load_data()
 
        c.perm_user = AuthUser(user_id=self.authuser.user_id,
 
                               ip_addr=self.ip_addr)
 
        c.extern_type = c.user.extern_type
 
        c.extern_name = c.user.extern_name
 

	
 
        defaults = c.user.get_dict()
 
        update = False
 
        if request.POST:
 
            _form = UserForm(edit=True,
 
                             old_data={'user_id': self.authuser.user_id,
 
                                       'email': self.authuser.email})()
 
            form_result = {}
 
            try:
 
                post_data = dict(request.POST)
 
                post_data['new_password'] = ''
 
                post_data['password_confirmation'] = ''
 
                form_result = _form.to_python(post_data)
 
                # skip updating those attrs for my account
 
                skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
 
                              'new_password', 'password_confirmation']
 
                #TODO: plugin should define if username can be updated
 
                if c.extern_type != "internal":
 
                    # forbid updating username for external accounts
 
                    skip_attrs.append('username')
 

	
 
                UserModel().update(self.authuser.user_id, form_result,
 
                                   skip_attrs=skip_attrs)
 
                h.flash(_('Your account was updated successfully'),
 
                        category='success')
 
                Session().commit()
 
                update = True
 

	
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('admin/my_account/my_account.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during update of user %s') \
 
                        % form_result.get('username'), category='error')
 
        if update:
 
            return redirect('my_account')
 
        return htmlfill.render(
 
            render('admin/my_account/my_account.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    def my_account_password(self):
 
        c.active = 'password'
 
        self.__load_data()
 
        if request.POST:
 
            _form = PasswordChangeForm(self.authuser.username)()
 
            try:
 
                form_result = _form.to_python(request.POST)
 
                UserModel().update(self.authuser.user_id, form_result)
 
                Session().commit()
 
                h.flash(_("Successfully updated password"), category='success')
 
            except formencode.Invalid as errors:
 
                return htmlfill.render(
 
                    render('admin/my_account/my_account.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during update of user password'),
 
                        category='error')
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_repos(self):
 
        c.active = 'repos'
 
        self.__load_data()
 

	
 
        #json used to render the grid
 
        c.data = self._load_my_repos_data()
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_watched(self):
 
        c.active = 'watched'
 
        self.__load_data()
 

	
 
        #json used to render the grid
 
        c.data = self._load_my_repos_data(watched=True)
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_perms(self):
 
        c.active = 'perms'
 
        self.__load_data()
 
        c.perm_user = AuthUser(user_id=self.authuser.user_id,
 
                               ip_addr=self.ip_addr)
 

	
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_emails(self):
 
        c.active = 'emails'
 
        self.__load_data()
 

	
 
        c.user_email_map = UserEmailMap.query()\
 
            .filter(UserEmailMap.user == c.user).all()
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_emails_add(self):
 
        email = request.POST.get('new_email')
 

	
 
        try:
 
            UserModel().add_extra_email(self.authuser.user_id, email)
 
            Session().commit()
 
            h.flash(_("Added email %s to user") % email, category='success')
 
        except formencode.Invalid, error:
 
            msg = error.error_dict['email']
 
            h.flash(msg, category='error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during email saving'),
 
                    category='error')
 
        return redirect(url('my_account_emails'))
 

	
 
    def my_account_emails_delete(self):
 
        email_id = request.POST.get('del_email_id')
 
        user_model = UserModel()
 
        user_model.delete_extra_email(self.authuser.user_id, email_id)
 
        Session().commit()
 
        h.flash(_("Removed email from user"), category='success')
 
        return redirect(url('my_account_emails'))
 

	
 
    def my_account_pullrequests(self):
 
        c.active = 'pullrequests'
 
        self.__load_data()
 
        c.show_closed = request.GET.get('pr_show_closed')
 

	
 
        def _filter(pr):
 
            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
 
            if not c.show_closed:
 
                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
 
            return s
 

	
 
        c.my_pull_requests = _filter(PullRequest.query()\
 
                                .filter(PullRequest.user_id ==
 
                                        self.authuser.user_id)\
 
                                .all())
 
        my_prs = [x.pull_request for x in PullRequestReviewers.query()
 
                    .filter(PullRequestReviewers.user_id ==
 
                        self.authuser.user_id).all()]
 
        c.participate_in_pull_requests = _filter(my_prs)
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_api_keys(self):
 
        c.active = 'api_keys'
 
        self.__load_data()
 
        show_expired = True
 
        c.lifetime_values = [
 
            (str(-1), _('forever')),
 
            (str(5), _('5 minutes')),
 
            (str(60), _('1 hour')),
 
            (str(60 * 24), _('1 day')),
 
            (str(60 * 24 * 30), _('1 month')),
 
        ]
 
        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 
        c.user_api_keys = ApiKeyModel().get_api_keys(self.authuser.user_id,
 
                                                     show_expired=show_expired)
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_api_keys_add(self):
 
        lifetime = safe_int(request.POST.get('lifetime'), -1)
 
        description = request.POST.get('description')
 
        new_api_key = ApiKeyModel().create(self.authuser.user_id,
 
                                           description, lifetime)
 
        Session().commit()
 
        h.flash(_("Api key successfully created"), category='success')
 
        return redirect(url('my_account_api_keys'))
 

	
 
    def my_account_api_keys_delete(self):
 
        api_key = request.POST.get('del_api_key')
 
        user_id = self.authuser.user_id
 
        if request.POST.get('del_api_key_builtin'):
 
            user = User.get(user_id)
 
            if user:
 
                user.api_key = generate_api_key(user.username)
 
                Session().add(user)
 
                Session().commit()
 
                h.flash(_("Api key successfully reset"), category='success')
 
        elif api_key:
 
            ApiKeyModel().delete(api_key, self.authuser.user_id)
 
            Session().commit()
 
            h.flash(_("Api key successfully deleted"), category='success')
 

	
 
        return redirect(url('my_account_api_keys'))
kallithea/controllers/admin/notifications.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.notifications
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
notifications controller for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Nov 23, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 

	
 
from pylons import request
 
from pylons import tmpl_context as c, url
 
from pylons.controllers.util import redirect, abort
 
from webob.exc import HTTPBadRequest
 

	
 
from kallithea.model.db import Notification
 
from kallithea.model.notification import NotificationModel
 
from kallithea.model.meta import Session
 
from kallithea.lib.auth import LoginRequired, NotAnonymous
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib import helpers as h
 
from kallithea.lib.helpers import Page
 
from kallithea.lib.utils2 import safe_int
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class NotificationsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('notification', 'notifications', controller='_admin/notifications',
 
    #         path_prefix='/_admin', name_prefix='_admin_')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def __before__(self):
 
        super(NotificationsController, self).__before__()
 

	
 
    def index(self, format='html'):
 
        """GET /_admin/notifications: All items in the collection"""
 
        # url('notifications')
 
        c.user = self.authuser
 
        notif = NotificationModel().get_for_user(self.authuser.user_id,
 
                                            filter_=request.GET.getall('type'))
 

	
 
        p = safe_int(request.GET.get('page', 1), 1)
 
        c.notifications = Page(notif, page=p, items_per_page=10)
 
        c.pull_request_type = Notification.TYPE_PULL_REQUEST
 
        c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
 
                          Notification.TYPE_PULL_REQUEST_COMMENT]
 

	
 
        _current_filter = request.GET.getall('type')
 
        c.current_filter = 'all'
 
        if _current_filter == [c.pull_request_type]:
 
            c.current_filter = 'pull_request'
 
        elif _current_filter == c.comment_type:
 
            c.current_filter = 'comment'
 

	
 
        return render('admin/notifications/notifications.html')
 

	
 
    def mark_all_read(self):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            nm = NotificationModel()
 
            # mark all read
 
            nm.mark_all_read_for_user(self.authuser.user_id,
 
                                      filter_=request.GET.getall('type'))
 
            Session().commit()
 
            c.user = self.authuser
 
            notif = nm.get_for_user(self.authuser.user_id,
 
                                    filter_=request.GET.getall('type'))
 
            c.notifications = Page(notif, page=1, items_per_page=10)
 
            return render('admin/notifications/notifications_data.html')
 

	
 
    def create(self):
 
        """POST /_admin/notifications: Create a new item"""
 
        # url('notifications')
 

	
 
    def new(self, format='html'):
 
        """GET /_admin/notifications/new: Form to create a new item"""
 
        # url('new_notification')
 

	
 
    def update(self, notification_id):
 
        """PUT /_admin/notifications/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('notification', notification_id=ID),
 
        #           method='put')
 
        # url('notification', notification_id=ID)
 
        try:
 
            no = Notification.get(notification_id)
 
            owner = all(un.user.user_id == c.authuser.user_id
 
                        for un in no.notifications_to_users)
 
            if h.HasPermissionAny('hg.admin')() or owner:
 
                # deletes only notification2user
 
                NotificationModel().mark_read(c.authuser.user_id, no)
 
                Session().commit()
 
                return 'ok'
 
        except Exception:
 
            Session().rollback()
 
            log.error(traceback.format_exc())
 
        raise HTTPBadRequest()
 

	
 
    def delete(self, notification_id):
 
        """DELETE /_admin/notifications/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('notification', notification_id=ID),
 
        #           method='delete')
 
        # url('notification', notification_id=ID)
 
        try:
 
            no = Notification.get(notification_id)
 
            owner = any(un.user.user_id == c.authuser.user_id
 
                        for un in no.notifications_to_users)
 
            if h.HasPermissionAny('hg.admin')() or owner:
 
                # deletes only notification2user
 
                NotificationModel().delete(c.authuser.user_id, no)
 
                Session().commit()
 
                return 'ok'
 
        except Exception:
 
            Session().rollback()
 
            log.error(traceback.format_exc())
 
        raise HTTPBadRequest()
 

	
 
    def show(self, notification_id, format='html'):
 
        """GET /_admin/notifications/id: Show a specific item"""
 
        # url('notification', notification_id=ID)
 
        c.user = self.authuser
 
        no = Notification.get(notification_id)
 

	
 
        owner = any(un.user.user_id == c.authuser.user_id
 
                    for un in no.notifications_to_users)
 
        repo_admin = h.HasRepoPermissionAny('repository.admin')
 
        if no and (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
 
            unotification = NotificationModel()\
 
                            .get_user_notification(c.user.user_id, no)
 

	
 
            # if this association to user is not valid, we don't want to show
 
            # this message
 
            if unotification:
 
                if not unotification.read:
 
                    unotification.mark_as_read()
 
                    Session().commit()
 
                c.notification = no
 

	
 
                return render('admin/notifications/show_notification.html')
 

	
 
        return abort(403)
 

	
 
    def edit(self, notification_id, format='html'):
 
        """GET /_admin/notifications/id/edit: Form to edit an existing item"""
 
        # url('edit_notification', notification_id=ID)
kallithea/controllers/admin/permissions.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.permissions
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
permissions controller for Rhodecode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 27, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
import logging
 
import traceback
 
import formencode
 
from formencode import htmlfill
 

	
 
from pylons import request, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator,\
 
    AuthUser
 
from kallithea.lib.base import BaseController, render
 
from kallithea.model.forms import DefaultPermissionsForm
 
from kallithea.model.permission import PermissionModel
 
from kallithea.model.db import User, UserIpMap, Permission
 
from kallithea.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class PermissionsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('permission', 'permissions')
 

	
 
    @LoginRequired()
 
    @HasPermissionAllDecorator('hg.admin')
 
    def __before__(self):
 
        super(PermissionsController, self).__before__()
 

	
 
    def __load_data(self):
 
        c.repo_perms_choices = [('repository.none', _('None'),),
 
                                   ('repository.read', _('Read'),),
 
                                   ('repository.write', _('Write'),),
 
                                   ('repository.admin', _('Admin'),)]
 
        c.group_perms_choices = [('group.none', _('None'),),
 
                                 ('group.read', _('Read'),),
 
                                 ('group.write', _('Write'),),
 
                                 ('group.admin', _('Admin'),)]
 
        c.user_group_perms_choices = [('usergroup.none', _('None'),),
 
                                      ('usergroup.read', _('Read'),),
 
                                      ('usergroup.write', _('Write'),),
 
                                      ('usergroup.admin', _('Admin'),)]
 
        c.register_choices = [
 
            ('hg.register.none',
 
                _('Disabled')),
 
            ('hg.register.manual_activate',
 
                _('Allowed with manual account activation')),
 
            ('hg.register.auto_activate',
 
                _('Allowed with automatic account activation')), ]
 

	
 
        c.extern_activate_choices = [
 
            ('hg.extern_activate.manual', _('Manual activation of external account')),
 
            ('hg.extern_activate.auto', _('Automatic activation of external account')),
 
        ]
 

	
 
        c.repo_create_choices = [('hg.create.none', _('Disabled')),
 
                                 ('hg.create.repository', _('Enabled'))]
 

	
 
        c.repo_create_on_write_choices = [
 
            ('hg.create.write_on_repogroup.true', _('Enabled')),
 
            ('hg.create.write_on_repogroup.false', _('Disabled')),
 
        ]
 

	
 
        c.user_group_create_choices = [('hg.usergroup.create.false', _('Disabled')),
 
                                       ('hg.usergroup.create.true', _('Enabled'))]
 

	
 
        c.repo_group_create_choices = [('hg.repogroup.create.false', _('Disabled')),
 
                                       ('hg.repogroup.create.true', _('Enabled'))]
 

	
 
        c.fork_choices = [('hg.fork.none', _('Disabled')),
 
                          ('hg.fork.repository', _('Enabled'))]
 

	
 
    def permission_globals(self):
 
        c.active = 'globals'
 
        self.__load_data()
 
        if request.POST:
 
            _form = DefaultPermissionsForm(
 
                [x[0] for x in c.repo_perms_choices],
 
                [x[0] for x in c.group_perms_choices],
 
                [x[0] for x in c.user_group_perms_choices],
 
                [x[0] for x in c.repo_create_choices],
 
                [x[0] for x in c.repo_create_on_write_choices],
 
                [x[0] for x in c.repo_group_create_choices],
 
                [x[0] for x in c.user_group_create_choices],
 
                [x[0] for x in c.fork_choices],
 
                [x[0] for x in c.register_choices],
 
                [x[0] for x in c.extern_activate_choices])()
 

	
 
            try:
 
                form_result = _form.to_python(dict(request.POST))
 
                form_result.update({'perm_user_name': 'default'})
 
                PermissionModel().update(form_result)
 
                Session().commit()
 
                h.flash(_('Global permissions updated successfully'),
 
                        category='success')
 

	
 
            except formencode.Invalid, errors:
 
                defaults = errors.value
 

	
 
                return htmlfill.render(
 
                    render('admin/permissions/permissions.html'),
 
                    defaults=defaults,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during update of permissions'),
 
                        category='error')
 

	
 
            return redirect(url('admin_permissions'))
 

	
 
        c.user = User.get_default_user()
 
        defaults = {'anonymous': c.user.active}
 

	
 
        for p in c.user.user_perms:
 
            if p.permission.permission_name.startswith('repository.'):
 
                defaults['default_repo_perm'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('group.'):
 
                defaults['default_group_perm'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('usergroup.'):
 
                defaults['default_user_group_perm'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('hg.create.write_on_repogroup'):
 
                defaults['create_on_write'] = p.permission.permission_name
 

	
 
            elif p.permission.permission_name.startswith('hg.create.'):
 
                defaults['default_repo_create'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('hg.repogroup.'):
 
                defaults['default_repo_group_create'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('hg.usergroup.'):
 
                defaults['default_user_group_create'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('hg.register.'):
 
                defaults['default_register'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('hg.extern_activate.'):
 
                defaults['default_extern_activate'] = p.permission.permission_name
 

	
 
            if p.permission.permission_name.startswith('hg.fork.'):
 
                defaults['default_fork'] = p.permission.permission_name
 

	
 
        return htmlfill.render(
 
            render('admin/permissions/permissions.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def permission_ips(self):
 
        c.active = 'ips'
 
        c.user = User.get_default_user()
 
        c.user_ip_map = UserIpMap.query()\
 
                        .filter(UserIpMap.user == c.user).all()
 

	
 
        return render('admin/permissions/permissions.html')
 

	
 
    def permission_perms(self):
 
        c.active = 'perms'
 
        c.user = User.get_default_user()
 
        c.perm_user = c.user.AuthUser
 
        return render('admin/permissions/permissions.html')
kallithea/controllers/admin/repo_groups.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.repo_groups
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Repository groups controller for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Mar 23, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
import formencode
 
import itertools
 

	
 
from formencode import htmlfill
 

	
 
from pylons import request, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _, ungettext
 

	
 
from sqlalchemy.exc import IntegrityError
 

	
 
import kallithea
 
from kallithea.lib import helpers as h
 
from kallithea.lib.compat import json
 
from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
 
    HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAll,\
 
    HasPermissionAll
 
from kallithea.lib.base import BaseController, render
 
from kallithea.model.db import RepoGroup, Repository
 
from kallithea.model.scm import RepoGroupList
 
from kallithea.model.repo_group import RepoGroupModel
 
from kallithea.model.forms import RepoGroupForm, RepoGroupPermsForm
 
from kallithea.model.meta import Session
 
from kallithea.model.repo import RepoModel
 
from webob.exc import HTTPInternalServerError, HTTPNotFound
 
from kallithea.lib.utils2 import str2bool, safe_int
 
from sqlalchemy.sql.expression import func
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class RepoGroupsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(RepoGroupsController, self).__before__()
 

	
 
    def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
 
        if HasPermissionAll('hg.admin')('group edit'):
 
            #we're global admin, we're ok and we can create TOP level groups
 
            allow_empty_group = True
 

	
 
        #override the choices for this form, we need to filter choices
 
        #and display only those we have ADMIN right
 
        groups_with_admin_rights = RepoGroupList(RepoGroup.query().all(),
 
                                                 perm_set=['group.admin'])
 
        c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
 
                                                 show_empty_group=allow_empty_group)
 
        # exclude filtered ids
 
        c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
 
                               c.repo_groups)
 
        c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.user_groups_array = repo_model.get_user_groups_js()
 

	
 
    def __load_data(self, group_id):
 
        """
 
        Load defaults settings for edit, and update
 

	
 
        :param group_id:
 
        """
 
        repo_group = RepoGroup.get_or_404(group_id)
 
        data = repo_group.get_dict()
 
        data['group_name'] = repo_group.name
 

	
 
        # fill repository group users
 
        for p in repo_group.repo_group_to_perm:
 
            data.update({'u_perm_%s' % p.user.username:
 
                             p.permission.permission_name})
 

	
 
        # fill repository group groups
 
        for p in repo_group.users_group_to_perm:
 
            data.update({'g_perm_%s' % p.users_group.users_group_name:
 
                             p.permission.permission_name})
 

	
 
        return data
 

	
 
    def _revoke_perms_on_yourself(self, form_result):
 
        _up = filter(lambda u: c.authuser.username == u[0],
 
                     form_result['perms_updates'])
 
        _new = filter(lambda u: c.authuser.username == u[0],
 
                      form_result['perms_new'])
 
        if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
 
            return True
 
        return False
 

	
 
    def index(self, format='html'):
 
        """GET /repo_groups: All items in the collection"""
 
        # url('repos_groups')
 
        _list = RepoGroup.query()\
 
                    .order_by(func.lower(RepoGroup.group_name))\
 
                    .all()
 
        group_iter = RepoGroupList(_list, perm_set=['group.admin'])
 
        repo_groups_data = []
 
        total_records = len(group_iter)
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        repo_group_name = lambda repo_group_name, children_groups: (
 
            template.get_def("repo_group_name")
 
            .render(repo_group_name, children_groups, _=_, h=h, c=c)
 
        )
 
        repo_group_actions = lambda repo_group_id, repo_group_name, gr_count: (
 
            template.get_def("repo_group_actions")
 
            .render(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
 
                    ungettext=ungettext)
 
        )
 

	
 
        for repo_gr in group_iter:
 
            children_groups = map(h.safe_unicode,
 
                itertools.chain((g.name for g in repo_gr.parents),
 
                                (x.name for x in [repo_gr])))
 
            repo_count = repo_gr.repositories.count()
 
            repo_groups_data.append({
 
                "raw_name": repo_gr.group_name,
 
                "group_name": repo_group_name(repo_gr.group_name, children_groups),
 
                "desc": repo_gr.group_description,
 
                "repos": repo_count,
 
                "owner": h.person(repo_gr.user.username),
 
                "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
 
                                             repo_count)
 
            })
 

	
 
        c.data = json.dumps({
 
            "totalRecords": total_records,
 
            "startIndex": 0,
 
            "sort": None,
 
            "dir": "asc",
 
            "records": repo_groups_data
 
        })
 

	
 
        return render('admin/repo_groups/repo_groups.html')
 

	
 
    def create(self):
 
        """POST /repo_groups: Create a new item"""
 
        # url('repos_groups')
 

	
 
        self.__load_defaults()
 

	
 
        # permissions for can create group based on parent_id are checked
 
        # here in the Form
 
        repo_group_form = RepoGroupForm(available_groups=
 
                                map(lambda k: unicode(k[0]), c.repo_groups))()
 
        try:
 
            form_result = repo_group_form.to_python(dict(request.POST))
 
            RepoGroupModel().create(
 
                group_name=form_result['group_name'],
 
                group_description=form_result['group_description'],
 
                parent=form_result['group_parent_id'],
 
                owner=self.authuser.user_id,
 
                copy_permissions=form_result['group_copy_permissions']
 
            )
 
            Session().commit()
 
            h.flash(_('Created repository group %s') \
 
                    % form_result['group_name'], category='success')
 
            #TODO: in futureaction_logger(, '', '', '', self.sa)
 
        except formencode.Invalid, errors:
 
            return htmlfill.render(
 
                render('admin/repo_groups/repo_group_add.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during creation of repository group %s') \
 
                    % request.POST.get('group_name'), category='error')
 
        parent_group_id = form_result['group_parent_id']
 
        #TODO: maybe we should get back to the main view, not the admin one
 
        return redirect(url('repos_groups', parent_group=parent_group_id))
 

	
 
    def new(self):
 
        """GET /repo_groups/new: Form to create a new item"""
 
        # url('new_repos_group')
 
        if HasPermissionAll('hg.admin')('group create'):
 
            #we're global admin, we're ok and we can create TOP level groups
 
            pass
 
        else:
 
            # we pass in parent group into creation form, thus we know
 
            # what would be the group, we can check perms here !
 
            group_id = safe_int(request.GET.get('parent_group'))
 
            group = RepoGroup.get(group_id) if group_id else None
 
            group_name = group.group_name if group else None
 
            if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'):
 
                pass
 
            else:
 
                return abort(403)
 

	
 
        self.__load_defaults()
 
        return render('admin/repo_groups/repo_group_add.html')
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.admin')
 
    def update(self, group_name):
 
        """PUT /repo_groups/group_name: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('repos_group', group_name=GROUP_NAME),
 
        #           method='put')
 
        # url('repos_group', group_name=GROUP_NAME)
 

	
 
        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
 
        if HasPermissionAll('hg.admin')('group edit'):
 
            #we're global admin, we're ok and we can create TOP level groups
 
            allow_empty_group = True
 
        elif not c.repo_group.parent_group:
 
            allow_empty_group = True
 
        else:
 
            allow_empty_group = False
 
        self.__load_defaults(allow_empty_group=allow_empty_group,
 
                             exclude_group_ids=[c.repo_group.group_id])
 

	
 
        repo_group_form = RepoGroupForm(
 
            edit=True,
 
            old_data=c.repo_group.get_dict(),
 
            available_groups=c.repo_groups_choices,
 
            can_create_in_root=allow_empty_group,
 
        )()
 
        try:
 
            form_result = repo_group_form.to_python(dict(request.POST))
 

	
 
            new_gr = RepoGroupModel().update(group_name, form_result)
 
            Session().commit()
 
            h.flash(_('Updated repository group %s') \
 
                    % form_result['group_name'], category='success')
 
            # we now have new name !
 
            group_name = new_gr.group_name
 
            #TODO: in future action_logger(, '', '', '', self.sa)
 
        except formencode.Invalid, errors:
 

	
 
            return htmlfill.render(
 
                render('admin/repo_groups/repo_group_edit.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during update of repository group %s') \
 
                    % request.POST.get('group_name'), category='error')
 

	
 
        return redirect(url('edit_repo_group', group_name=group_name))
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.admin')
 
    def delete(self, group_name):
 
        """DELETE /repo_groups/group_name: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('repos_group', group_name=GROUP_NAME),
 
        #           method='delete')
 
        # url('repos_group', group_name=GROUP_NAME)
 

	
 
        gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
 
        repos = gr.repositories.all()
 
        if repos:
 
            h.flash(_('This group contains %s repositores and cannot be '
 
                      'deleted') % len(repos), category='warning')
 
            return redirect(url('repos_groups'))
 

	
 
        children = gr.children.all()
 
        if children:
 
            h.flash(_('This group contains %s subgroups and cannot be deleted'
 
                      % (len(children))), category='warning')
 
            return redirect(url('repos_groups'))
 

	
 
        try:
 
            RepoGroupModel().delete(group_name)
 
            Session().commit()
 
            h.flash(_('Removed repository group %s') % group_name,
 
                    category='success')
 
            #TODO: in future action_logger(, '', '', '', self.sa)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during deletion of repository group %s')
 
                    % group_name, category='error')
 

	
 
        return redirect(url('repos_groups'))
 

	
 
    def show_by_name(self, group_name):
 
        """
 
        This is a proxy that does a lookup group_name -> id, and shows
 
        the group by id view instead
 
        """
 
        group_name = group_name.rstrip('/')
 
        id_ = RepoGroup.get_by_group_name(group_name)
 
        if id_:
 
            return self.show(group_name)
 
        raise HTTPNotFound
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
 
                                         'group.admin')
 
    def show(self, group_name):
 
        """GET /repo_groups/group_name: Show a specific item"""
 
        # url('repos_group', group_name=GROUP_NAME)
 
        c.active = 'settings'
 

	
 
        c.group = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
 
        c.group_repos = c.group.repositories.all()
 

	
 
        #overwrite our cached list with current filter
 
        gr_filter = c.group_repos
 
        c.repo_cnt = 0
 

	
 
        groups = RepoGroup.query().order_by(RepoGroup.group_name)\
 
            .filter(RepoGroup.group_parent_id == c.group.group_id).all()
 
        c.groups = self.scm_model.get_repo_groups(groups)
 

	
 
        c.repos_list = Repository.query()\
 
                        .filter(Repository.group_id == c.group.group_id)\
 
                        .order_by(func.lower(Repository.repo_name))\
 
                        .all()
 

	
 
        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
 
                                                   admin=False)
 
        #json used to render the grid
 
        c.data = json.dumps(repos_data)
 

	
 
        return render('admin/repo_groups/repo_group_show.html')
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.admin')
 
    def edit(self, group_name):
 
        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
 
        # url('edit_repo_group', group_name=GROUP_NAME)
 
        c.active = 'settings'
 

	
 
        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
 
        #we can only allow moving empty group if it's already a top-level
 
        #group, ie has no parents, or we're admin
 
        if HasPermissionAll('hg.admin')('group edit'):
 
            #we're global admin, we're ok and we can create TOP level groups
 
            allow_empty_group = True
 
        elif not c.repo_group.parent_group:
 
            allow_empty_group = True
 
        else:
 
            allow_empty_group = False
 

	
 
        self.__load_defaults(allow_empty_group=allow_empty_group,
 
                             exclude_group_ids=[c.repo_group.group_id])
 
        defaults = self.__load_data(c.repo_group.group_id)
 

	
 
        return htmlfill.render(
 
            render('admin/repo_groups/repo_group_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.admin')
 
    def edit_repo_group_advanced(self, group_name):
 
        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
 
        # url('edit_repo_group', group_name=GROUP_NAME)
 
        c.active = 'advanced'
 
        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
 

	
 
        return render('admin/repo_groups/repo_group_edit.html')
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.admin')
 
    def edit_repo_group_perms(self, group_name):
 
        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
 
        # url('edit_repo_group', group_name=GROUP_NAME)
 
        c.active = 'perms'
 
        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
 
        self.__load_defaults()
 
        defaults = self.__load_data(c.repo_group.group_id)
 

	
 
        return htmlfill.render(
 
            render('admin/repo_groups/repo_group_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    @HasRepoGroupPermissionAnyDecorator('group.admin')
 
    def update_perms(self, group_name):
 
        """
kallithea/controllers/admin/repos.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.repos
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Repositories controller for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 7, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
import formencode
 
from formencode import htmlfill
 
from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound
 
from pylons import request, tmpl_context as c, url
 
from pylons.controllers.util import redirect
 
from pylons.i18n.translation import _
 
from sqlalchemy.sql.expression import func
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
 
    HasRepoPermissionAllDecorator, NotAnonymous,HasPermissionAny, \
 
    HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.utils import action_logger, repo_name_slug, jsonify
 
from kallithea.lib.helpers import get_token
 
from kallithea.lib.vcs import RepositoryError
 
from kallithea.model.meta import Session
 
from kallithea.model.db import User, Repository, UserFollowing, RepoGroup,\
 
    Setting, RepositoryField
 
from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
 
from kallithea.model.scm import ScmModel, RepoGroupList, RepoList
 
from kallithea.model.repo import RepoModel
 
from kallithea.lib.compat import json
 
from kallithea.lib.exceptions import AttachedForksError
 
from kallithea.lib.utils2 import safe_int
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ReposController(BaseRepoController):
 
    """
 
    REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('repo', 'repos')
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(ReposController, self).__before__()
 

	
 
    def _load_repo(self, repo_name):
 
        repo_obj = Repository.get_by_repo_name(repo_name)
 

	
 
        if repo_obj is None:
 
            h.not_mapped_error(repo_name)
 
            return redirect(url('repos'))
 

	
 
        return repo_obj
 

	
 
    def __load_defaults(self, repo=None):
 
        acl_groups = RepoGroupList(RepoGroup.query().all(),
 
                               perm_set=['group.write', 'group.admin'])
 
        c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
 
        c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 

	
 
        # in case someone no longer have a group.write access to a repository
 
        # pre fill the list with this entry, we don't care if this is the same
 
        # but it will allow saving repo data properly.
 

	
 
        repo_group = None
 
        if repo:
 
            repo_group = repo.group
 
        if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
 
            c.repo_groups_choices.append(unicode(repo_group.group_id))
 
            c.repo_groups.append(RepoGroup._generate_choice(repo_group))
 

	
 
        choices, c.landing_revs = ScmModel().get_repo_landing_revs()
 
        c.landing_revs_choices = choices
 

	
 
    def __load_data(self, repo_name=None):
 
        """
 
        Load defaults settings for edit, and update
 

	
 
        :param repo_name:
 
        """
 
        c.repo_info = self._load_repo(repo_name)
 
        self.__load_defaults(c.repo_info)
 

	
 
        ##override defaults for exact repo info here git/hg etc
 
        choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
 
        c.landing_revs_choices = choices
 
        defaults = RepoModel()._get_defaults(repo_name)
 

	
 
        return defaults
 

	
 
    def index(self, format='html'):
 
        """GET /repos: All items in the collection"""
 
        # url('repos')
 
        _list = Repository.query()\
 
                        .order_by(func.lower(Repository.repo_name))\
 
                        .all()
 

	
 
        c.repos_list = RepoList(_list, perm_set=['repository.admin'])
 
        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
 
                                                   admin=True,
 
                                                   super_user_actions=True)
 
        #json used to render the grid
 
        c.data = json.dumps(repos_data)
 

	
 
        return render('admin/repos/repos.html')
 

	
 
    @NotAnonymous()
 
    def create(self):
 
        """
 
        POST /repos: Create a new item"""
 
        # url('repos')
 

	
 
        self.__load_defaults()
 
        form_result = {}
 
        task_id = None
 
        try:
 
            # CanWriteToGroup validators checks permissions of this POST
 
            form_result = RepoForm(repo_groups=c.repo_groups_choices,
 
                                   landing_revs=c.landing_revs_choices)()\
 
                            .to_python(dict(request.POST))
 

	
 
            # create is done sometimes async on celery, db transaction
 
            # management is handled there.
 
            task = RepoModel().create(form_result, self.authuser.user_id)
 
            from celery.result import BaseAsyncResult
 
            if isinstance(task, BaseAsyncResult):
 
                task_id = task.task_id
 
        except formencode.Invalid, errors:
 
            return htmlfill.render(
 
                render('admin/repos/repo_add.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 

	
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            msg = (_('Error creating repository %s')
 
                   % form_result.get('repo_name'))
 
            h.flash(msg, category='error')
 
            return redirect(url('home'))
 

	
 
        return redirect(h.url('repo_creating_home',
 
                              repo_name=form_result['repo_name_full'],
 
                              task_id=task_id))
 

	
 
    @NotAnonymous()
 
    def create_repository(self):
 
        """GET /_admin/create_repository: Form to create a new item"""
 
        new_repo = request.GET.get('repo', '')
 
        parent_group = request.GET.get('parent_group')
 
        if not HasPermissionAny('hg.admin', 'hg.create.repository')():
 
            #you're not super admin nor have global create permissions,
 
            #but maybe you have at least write permission to a parent group ?
 
            _gr = RepoGroup.get(parent_group)
 
            gr_name = _gr.group_name if _gr else None
 
            # create repositories with write permission on group is set to true
 
            create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
 
            group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
 
            group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
 
            if not (group_admin or (group_write and create_on_write)):
 
                raise HTTPForbidden
 

	
 
        acl_groups = RepoGroupList(RepoGroup.query().all(),
 
                               perm_set=['group.write', 'group.admin'])
 
        c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
 
        c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 
        choices, c.landing_revs = ScmModel().get_repo_landing_revs()
 

	
 
        c.new_repo = repo_name_slug(new_repo)
 

	
 
        ## apply the defaults from defaults page
 
        defaults = Setting.get_default_repo_settings(strip_prefix=True)
 
        if parent_group:
 
            defaults.update({'repo_group': parent_group})
 

	
 
        return htmlfill.render(
 
            render('admin/repos/repo_add.html'),
 
            defaults=defaults,
 
            errors={},
 
            prefix_error=False,
 
            encoding="UTF-8"
 
        )
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def repo_creating(self, repo_name):
 
        c.repo = repo_name
 
        c.task_id = request.GET.get('task_id')
 
        if not c.repo:
 
            raise HTTPNotFound()
 
        return render('admin/repos/repo_creating.html')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @jsonify
 
    def repo_check(self, repo_name):
 
        c.repo = repo_name
 
        task_id = request.GET.get('task_id')
 

	
 
        if task_id and task_id not in ['None']:
 
            from kallithea import CELERY_ON
 
            from celery.result import AsyncResult
 
            if CELERY_ON:
 
                task = AsyncResult(task_id)
 
                if task.failed():
 
                    raise HTTPInternalServerError(task.traceback)
 

	
 
        repo = Repository.get_by_repo_name(repo_name)
 
        if repo and repo.repo_state == Repository.STATE_CREATED:
 
            if repo.clone_uri:
 
                clone_uri = repo.clone_uri_hidden
 
                h.flash(_('Created repository %s from %s')
 
                        % (repo.repo_name, clone_uri), category='success')
 
            else:
 
                repo_url = h.link_to(repo.repo_name,
 
                                     h.url('summary_home',
 
                                           repo_name=repo.repo_name))
 
                fork = repo.fork
 
                if fork:
 
                    fork_name = fork.repo_name
 
                    h.flash(h.literal(_('Forked repository %s as %s')
 
                            % (fork_name, repo_url)), category='success')
 
                else:
 
                    h.flash(h.literal(_('Created repository %s') % repo_url),
 
                            category='success')
 
            return {'result': True}
 
        return {'result': False}
 

	
 
    @HasRepoPermissionAllDecorator('repository.admin')
 
    def update(self, repo_name):
 
        """
 
        PUT /repos/repo_name: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('repo', repo_name=ID),
 
        #           method='put')
 
        # url('repo', repo_name=ID)
 
        c.repo_info = self._load_repo(repo_name)
 
        c.active = 'settings'
 
        c.repo_fields = RepositoryField.query()\
 
            .filter(RepositoryField.repository == c.repo_info).all()
 
        self.__load_defaults(c.repo_info)
 

	
 
        repo_model = RepoModel()
 
        changed_name = repo_name
 
        #override the choices with extracted revisions !
 
        choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
 
        c.landing_revs_choices = choices
 
        repo = Repository.get_by_repo_name(repo_name)
 
        old_data = {
 
            'repo_name': repo_name,
 
            'repo_group': repo.group.get_dict() if repo.group else {},
 
            'repo_type': repo.repo_type,
 
        }
 
        _form = RepoForm(edit=True, old_data=old_data,
 
                         repo_groups=c.repo_groups_choices,
 
                         landing_revs=c.landing_revs_choices)()
 

	
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            repo = repo_model.update(repo_name, **form_result)
 
            ScmModel().mark_for_invalidation(repo_name)
 
            h.flash(_('Repository %s updated successfully') % repo_name,
 
                    category='success')
 
            changed_name = repo.repo_name
 
            action_logger(self.authuser, 'admin_updated_repo',
 
                              changed_name, self.ip_addr, self.sa)
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            defaults = self.__load_data(repo_name)
 
            defaults.update(errors.value)
 
            return htmlfill.render(
 
                render('admin/repos/repo_edit.html'),
 
                defaults=defaults,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 

	
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during update of repository %s') \
 
                    % repo_name, category='error')
 
        return redirect(url('edit_repo', repo_name=changed_name))
 

	
 
    @HasRepoPermissionAllDecorator('repository.admin')
 
    def delete(self, repo_name):
 
        """
 
        DELETE /repos/repo_name: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('repo', repo_name=ID),
 
        #           method='delete')
 
        # url('repo', repo_name=ID)
 

	
 
        repo_model = RepoModel()
 
        repo = repo_model.get_by_repo_name(repo_name)
 
        if not repo:
 
            h.not_mapped_error(repo_name)
 
            return redirect(url('repos'))
 
        try:
 
            _forks = repo.forks.count()
 
            handle_forks = None
 
            if _forks and request.POST.get('forks'):
 
                do = request.POST['forks']
 
                if do == 'detach_forks':
 
                    handle_forks = 'detach'
 
                    h.flash(_('Detached %s forks') % _forks, category='success')
 
                elif do == 'delete_forks':
 
                    handle_forks = 'delete'
 
                    h.flash(_('Deleted %s forks') % _forks, category='success')
 
            repo_model.delete(repo, forks=handle_forks)
 
            action_logger(self.authuser, 'admin_deleted_repo',
 
                  repo_name, self.ip_addr, self.sa)
 
            ScmModel().mark_for_invalidation(repo_name)
 
            h.flash(_('Deleted repository %s') % repo_name, category='success')
 
            Session().commit()
 
        except AttachedForksError:
 
            h.flash(_('Cannot delete %s it still contains attached forks')
 
                        % repo_name, category='warning')
 

	
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during deletion of %s') % repo_name,
 
                    category='error')
 

	
 
        return redirect(url('repos'))
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def show(self, repo_name, format='html'):
 
        """GET /repos/repo_name: Show a specific item"""
 
        # url('repo', repo_name=ID)
 

	
 
    @HasRepoPermissionAllDecorator('repository.admin')
 
    def edit(self, repo_name):
 
        """GET /repo_name/settings: Form to edit an existing item"""
 
        # url('edit_repo', repo_name=ID)
 
        defaults = self.__load_data(repo_name)
 
        if 'clone_uri' in defaults:
 
            del defaults['clone_uri']
 

	
 
        c.repo_fields = RepositoryField.query()\
 
            .filter(RepositoryField.repository == c.repo_info).all()
 
        c.active = 'settings'
 
        return htmlfill.render(
 
            render('admin/repos/repo_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    @HasRepoPermissionAllDecorator('repository.admin')
 
    def edit_permissions(self, repo_name):
 
        """GET /repo_name/settings: Form to edit an existing item"""
 
        # url('edit_repo', repo_name=ID)
 
        c.repo_info = self._load_repo(repo_name)
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.user_groups_array = repo_model.get_user_groups_js()
 
        c.active = 'permissions'
 
        defaults = RepoModel()._get_defaults(repo_name)
 

	
 
        return htmlfill.render(
 
            render('admin/repos/repo_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def edit_permissions_update(self, repo_name):
 
        form = RepoPermsForm()().to_python(request.POST)
 
        RepoModel()._update_permissions(repo_name, form['perms_new'],
 
                                        form['perms_updates'])
 
        #TODO: implement this
 
        #action_logger(self.authuser, 'admin_changed_repo_permissions',
 
        #              repo_name, self.ip_addr, self.sa)
 
        Session().commit()
 
        h.flash(_('Repository permissions updated'), category='success')
 
        return redirect(url('edit_repo_perms', repo_name=repo_name))
 

	
 
    def edit_permissions_revoke(self, repo_name):
 
        try:
 
            obj_type = request.POST.get('obj_type')
 
            obj_id = None
kallithea/controllers/admin/settings.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.settings
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
settings controller for rhodecode admin
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Jul 14, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import time
 
import logging
 
import traceback
 
import formencode
 

	
 
from formencode import htmlfill
 
from pylons import request, tmpl_context as c, url, config
 
from pylons.controllers.util import redirect
 
from pylons.i18n.translation import _
 

	
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.celerylib import tasks, run_task
 
from kallithea.lib.exceptions import HgsubversionImportError
 
from kallithea.lib.utils import repo2db_mapper, set_app_settings
 
from kallithea.model.db import Ui, Repository, Setting
 
from kallithea.model.forms import ApplicationSettingsForm, \
 
    ApplicationUiSettingsForm, ApplicationVisualisationForm
 
from kallithea.model.scm import ScmModel
 
from kallithea.model.notification import EmailNotificationModel
 
from kallithea.model.meta import Session
 
from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str
 
from kallithea.lib.compat import json
 
log = logging.getLogger(__name__)
 

	
 

	
 
class SettingsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('setting', 'settings', controller='admin/settings',
 
    #         path_prefix='/admin', name_prefix='admin_')
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(SettingsController, self).__before__()
 

	
 
    def _get_hg_ui_settings(self):
 
        ret = Ui.query().all()
 

	
 
        if not ret:
 
            raise Exception('Could not get application ui settings !')
 
        settings = {}
 
        for each in ret:
 
            k = each.ui_key
 
            v = each.ui_value
 
            if k == '/':
 
                k = 'root_path'
 

	
 
            if k == 'push_ssl':
 
                v = str2bool(v)
 

	
 
            if k.find('.') != -1:
 
                k = k.replace('.', '_')
 

	
 
            if each.ui_section in ['hooks', 'extensions']:
 
                v = each.ui_active
 

	
 
            settings[each.ui_section + '_' + k] = v
 
        return settings
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def settings_vcs(self):
 
        """GET /admin/settings: All items in the collection"""
 
        # url('admin_settings')
 
        c.active = 'vcs'
 
        if request.POST:
 
            application_form = ApplicationUiSettingsForm()()
 
            try:
 
                form_result = application_form.to_python(dict(request.POST))
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                     render('admin/settings/settings.html'),
 
                     defaults=errors.value,
 
                     errors=errors.error_dict or {},
 
                     prefix_error=False,
 
                     encoding="UTF-8"
 
                )
 

	
 
            try:
 
                sett = Ui.get_by_key('push_ssl')
 
                sett.ui_value = form_result['web_push_ssl']
 
                Session().add(sett)
 
                if c.visual.allow_repo_location_change:
 
                    sett = Ui.get_by_key('/')
 
                    sett.ui_value = form_result['paths_root_path']
 
                    Session().add(sett)
 

	
 
                #HOOKS
 
                sett = Ui.get_by_key(Ui.HOOK_UPDATE)
 
                sett.ui_active = form_result['hooks_changegroup_update']
 
                Session().add(sett)
 

	
 
                sett = Ui.get_by_key(Ui.HOOK_REPO_SIZE)
 
                sett.ui_active = form_result['hooks_changegroup_repo_size']
 
                Session().add(sett)
 

	
 
                sett = Ui.get_by_key(Ui.HOOK_PUSH)
 
                sett.ui_active = form_result['hooks_changegroup_push_logger']
 
                Session().add(sett)
 

	
 
                sett = Ui.get_by_key(Ui.HOOK_PULL)
 
                sett.ui_active = form_result['hooks_outgoing_pull_logger']
 

	
 
                Session().add(sett)
 

	
 
                ## EXTENSIONS
 
                sett = Ui.get_by_key('largefiles')
 
                if not sett:
 
                    #make one if it's not there !
 
                    sett = Ui()
 
                    sett.ui_key = 'largefiles'
 
                    sett.ui_section = 'extensions'
 
                sett.ui_active = form_result['extensions_largefiles']
 
                Session().add(sett)
 

	
 
                sett = Ui.get_by_key('hgsubversion')
 
                if not sett:
 
                    #make one if it's not there !
 
                    sett = Ui()
 
                    sett.ui_key = 'hgsubversion'
 
                    sett.ui_section = 'extensions'
 

	
 
                sett.ui_active = form_result['extensions_hgsubversion']
 
                if sett.ui_active:
 
                    try:
 
                        import hgsubversion  # pragma: no cover
 
                    except ImportError:
 
                        raise HgsubversionImportError
 
                Session().add(sett)
 

	
 
#                sett = Ui.get_by_key('hggit')
 
#                if not sett:
 
#                    #make one if it's not there !
 
#                    sett = Ui()
 
#                    sett.ui_key = 'hggit'
 
#                    sett.ui_section = 'extensions'
 
#
 
#                sett.ui_active = form_result['extensions_hggit']
 
#                Session().add(sett)
 

	
 
                Session().commit()
 

	
 
                h.flash(_('Updated VCS settings'), category='success')
 

	
 
            except HgsubversionImportError:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Unable to activate hgsubversion support. '
 
                          'The "hgsubversion" library is missing'),
 
                        category='error')
 

	
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during updating '
 
                          'application settings'), category='error')
 

	
 
        defaults = Setting.get_app_settings()
 
        defaults.update(self._get_hg_ui_settings())
 

	
 
        return htmlfill.render(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def settings_mapping(self):
 
        """GET /admin/settings/mapping: All items in the collection"""
 
        # url('admin_settings_mapping')
 
        c.active = 'mapping'
 
        if request.POST:
 
            rm_obsolete = request.POST.get('destroy', False)
 
            install_git_hooks = request.POST.get('hooks', False)
 
            invalidate_cache = request.POST.get('invalidate', False)
 
            log.debug('rescanning repo location with destroy obsolete=%s and '
 
                      'install git hooks=%s' % (rm_obsolete,install_git_hooks))
 

	
 
            if invalidate_cache:
 
                log.debug('invalidating all repositories cache')
 
                for repo in Repository.get_all():
 
                    ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
 

	
 
            filesystem_repos = ScmModel().repo_scan()
 
            added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
 
                                            install_git_hook=install_git_hooks)
 
            _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
 
            h.flash(_('Repositories successfully '
 
                      'rescanned added: %s ; removed: %s') %
 
                    (_repr(added), _repr(removed)),
 
                    category='success')
 
            return redirect(url('admin_settings_mapping'))
 

	
 
        defaults = Setting.get_app_settings()
 
        defaults.update(self._get_hg_ui_settings())
 

	
 
        return htmlfill.render(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def settings_global(self):
 
        """GET /admin/settings/global: All items in the collection"""
 
        # url('admin_settings_global')
 
        c.active = 'global'
 
        if request.POST:
 
            application_form = ApplicationSettingsForm()()
 
            try:
 
                form_result = application_form.to_python(dict(request.POST))
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('admin/settings/settings.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 

	
 
            try:
 
                sett1 = Setting.create_or_update('title',
 
                                            form_result['rhodecode_title'])
 
                Session().add(sett1)
 

	
 
                sett2 = Setting.create_or_update('realm',
 
                                            form_result['rhodecode_realm'])
 
                Session().add(sett2)
 

	
 
                sett3 = Setting.create_or_update('ga_code',
 
                                            form_result['rhodecode_ga_code'])
 
                Session().add(sett3)
 

	
 
                sett4 = Setting.create_or_update('captcha_public_key',
 
                                    form_result['rhodecode_captcha_public_key'])
 
                Session().add(sett4)
 

	
 
                sett5 = Setting.create_or_update('captcha_private_key',
 
                                    form_result['rhodecode_captcha_private_key'])
 
                Session().add(sett5)
 

	
 
                Session().commit()
 
                set_app_settings(config)
 
                h.flash(_('Updated application settings'), category='success')
 

	
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during updating '
 
                          'application settings'),
 
                          category='error')
 

	
 
            return redirect(url('admin_settings_global'))
 

	
 
        defaults = Setting.get_app_settings()
 
        defaults.update(self._get_hg_ui_settings())
 

	
 
        return htmlfill.render(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def settings_visual(self):
 
        """GET /admin/settings/visual: All items in the collection"""
 
        # url('admin_settings_visual')
 
        c.active = 'visual'
 
        if request.POST:
 
            application_form = ApplicationVisualisationForm()()
 
            try:
 
                form_result = application_form.to_python(dict(request.POST))
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('admin/settings/settings.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8"
 
                )
 

	
 
            try:
 
                settings = [
 
                    ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
 
                    ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
 
                    ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
 
                    ('repository_fields', 'rhodecode_repository_fields', 'bool'),
 
                    ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
 
                    ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
 
                    ('show_version', 'rhodecode_show_version', 'bool'),
 
                    ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
 
                    ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
 
                    ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
 
                ]
 
                for setting, form_key, type_ in settings:
 
                    sett = Setting.create_or_update(setting,
 
                                        form_result[form_key], type_)
 
                    Session().add(sett)
 

	
 
                Session().commit()
 
                set_app_settings(config)
 
                h.flash(_('Updated visualisation settings'),
 
                        category='success')
 

	
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during updating '
 
                          'visualisation settings'),
 
                        category='error')
 

	
 
            return redirect(url('admin_settings_visual'))
 

	
 
        defaults = Setting.get_app_settings()
 
        defaults.update(self._get_hg_ui_settings())
 

	
 
        return htmlfill.render(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def settings_email(self):
 
        """GET /admin/settings/email: All items in the collection"""
 
        # url('admin_settings_email')
 
        c.active = 'email'
 
        if request.POST:
 
            test_email = request.POST.get('test_email')
 
            test_email_subj = 'RhodeCode test email'
 
            test_email_body = ('RhodeCode Email test, '
 
                               'RhodeCode version: %s' % c.kallithea_version)
 
            if not test_email:
 
                h.flash(_('Please enter email address'), category='error')
 
                return redirect(url('admin_settings_email'))
 

	
 
            test_email_html_body = EmailNotificationModel()\
 
                .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
 
                                body=test_email_body)
 

	
 
            recipients = [test_email] if test_email else None
 

	
 
            run_task(tasks.send_email, recipients, test_email_subj,
 
                     test_email_body, test_email_html_body)
 

	
 
            h.flash(_('Send email task created'), category='success')
 
            return redirect(url('admin_settings_email'))
 

	
 
        defaults = Setting.get_app_settings()
 
        defaults.update(self._get_hg_ui_settings())
 

	
 
        import kallithea
 
        c.rhodecode_ini = kallithea.CONFIG
 

	
 
        return htmlfill.render(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def settings_hooks(self):
 
        """GET /admin/settings/hooks: All items in the collection"""
 
        # url('admin_settings_hooks')
 
        c.active = 'hooks'
 
        if request.POST:
 
            if c.visual.allow_custom_hooks_settings:
 
                ui_key = request.POST.get('new_hook_ui_key')
 
                ui_value = request.POST.get('new_hook_ui_value')
 

	
 
                hook_id = request.POST.get('hook_id')
 

	
 
                try:
 
                    if ui_value and ui_key:
 
                        Ui.create_or_update_hook(ui_key, ui_value)
 
                        h.flash(_('Added new hook'), category='success')
 
                    elif hook_id:
 
                        Ui.delete(hook_id)
 
                        Session().commit()
 

	
 
                    # check for edits
 
                    update = False
 
                    _d = request.POST.dict_of_lists()
 
                    for k, v in zip(_d.get('hook_ui_key', []),
kallithea/controllers/admin/user_groups.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.users_groups
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
User Groups crud controller for pylons
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Jan 25, 2011
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
import formencode
 

	
 
from formencode import htmlfill
 
from pylons import request, session, tmpl_context as c, url, config
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from sqlalchemy.orm import joinedload
 
from sqlalchemy.sql.expression import func
 
from webob.exc import HTTPInternalServerError
 

	
 
import kallithea
 
from kallithea.lib import helpers as h
 
from kallithea.lib.exceptions import UserGroupsAssignedException,\
 
    RepoGroupAssignmentError
 
from kallithea.lib.utils2 import safe_unicode, str2bool, safe_int
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator,\
 
    HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator
 
from kallithea.lib.base import BaseController, render
 
from kallithea.model.scm import UserGroupList
 
from kallithea.model.user_group import UserGroupModel
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.db import User, UserGroup, UserGroupToPerm,\
 
    UserGroupRepoToPerm, UserGroupRepoGroupToPerm
 
from kallithea.model.forms import UserGroupForm, UserGroupPermsForm,\
 
    CustomDefaultPermissionsForm
 
from kallithea.model.meta import Session
 
from kallithea.lib.utils import action_logger
 
from kallithea.lib.compat import json
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class UserGroupsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(UserGroupsController, self).__before__()
 
        c.available_permissions = config['available_permissions']
 

	
 
    def __load_data(self, user_group_id):
 
        c.group_members_obj = sorted((x.user for x in c.user_group.members),
 
                                     key=lambda u: u.username.lower())
 

	
 
        c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
 
        c.available_members = sorted(((x.user_id, x.username) for x in
 
                                      User.query().all()),
 
                                     key=lambda u: u[1].lower())
 

	
 
    def __load_defaults(self, user_group_id):
 
        """
 
        Load defaults settings for edit, and update
 

	
 
        :param user_group_id:
 
        """
 
        user_group = UserGroup.get_or_404(user_group_id)
 
        data = user_group.get_dict()
 
        return data
 

	
 
    def index(self, format='html'):
 
        """GET /users_groups: All items in the collection"""
 
        # url('users_groups')
 
        _list = UserGroup.query()\
 
                        .order_by(func.lower(UserGroup.users_group_name))\
 
                        .all()
 
        group_iter = UserGroupList(_list, perm_set=['usergroup.admin'])
 
        user_groups_data = []
 
        total_records = len(group_iter)
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        user_group_name = lambda user_group_id, user_group_name: (
 
            template.get_def("user_group_name")
 
            .render(user_group_id, user_group_name, _=_, h=h, c=c)
 
        )
 
        user_group_actions = lambda user_group_id, user_group_name: (
 
            template.get_def("user_group_actions")
 
            .render(user_group_id, user_group_name, _=_, h=h, c=c)
 
        )
 
        for user_gr in group_iter:
 

	
 
            user_groups_data.append({
 
                "raw_name": user_gr.users_group_name,
 
                "group_name": user_group_name(user_gr.users_group_id,
 
                                              user_gr.users_group_name),
 
                "desc": user_gr.user_group_description,
 
                "members": len(user_gr.members),
 
                "active": h.boolicon(user_gr.users_group_active),
 
                "owner": h.person(user_gr.user.username),
 
                "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name)
 
            })
 

	
 
        c.data = json.dumps({
 
            "totalRecords": total_records,
 
            "startIndex": 0,
 
            "sort": None,
 
            "dir": "asc",
 
            "records": user_groups_data
 
        })
 

	
 
        return render('admin/user_groups/user_groups.html')
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
 
    def create(self):
 
        """POST /users_groups: Create a new item"""
 
        # url('users_groups')
 

	
 
        users_group_form = UserGroupForm()()
 
        try:
 
            form_result = users_group_form.to_python(dict(request.POST))
 
            UserGroupModel().create(name=form_result['users_group_name'],
 
                                    description=form_result['user_group_description'],
 
                                    owner=self.authuser.user_id,
 
                                    active=form_result['users_group_active'])
 

	
 
            gr = form_result['users_group_name']
 
            action_logger(self.authuser,
 
                          'admin_created_users_group:%s' % gr,
 
                          None, self.ip_addr, self.sa)
 
            h.flash(_('Created user group %s') % gr, category='success')
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            return htmlfill.render(
 
                render('admin/user_groups/user_group_add.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during creation of user group %s') \
 
                    % request.POST.get('users_group_name'), category='error')
 

	
 
        return redirect(url('users_groups'))
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
 
    def new(self, format='html'):
 
        """GET /user_groups/new: Form to create a new item"""
 
        # url('new_users_group')
 
        return render('admin/user_groups/user_group_add.html')
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def update(self, id):
 
        """PUT /user_groups/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('users_group', id=ID),
 
        #           method='put')
 
        # url('users_group', id=ID)
 

	
 
        c.user_group = UserGroup.get_or_404(id)
 
        c.active = 'settings'
 
        self.__load_data(id)
 

	
 
        available_members = [safe_unicode(x[0]) for x in c.available_members]
 

	
 
        users_group_form = UserGroupForm(edit=True,
 
                                         old_data=c.user_group.get_dict(),
 
                                         available_members=available_members)()
 

	
 
        try:
 
            form_result = users_group_form.to_python(request.POST)
 
            UserGroupModel().update(c.user_group, form_result)
 
            gr = form_result['users_group_name']
 
            action_logger(self.authuser,
 
                          'admin_updated_users_group:%s' % gr,
 
                          None, self.ip_addr, self.sa)
 
            h.flash(_('Updated user group %s') % gr, category='success')
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            ug_model = UserGroupModel()
 
            defaults = errors.value
 
            e = errors.error_dict or {}
 
            defaults.update({
 
                'create_repo_perm': ug_model.has_perm(id,
 
                                                      'hg.create.repository'),
 
                'fork_repo_perm': ug_model.has_perm(id,
 
                                                    'hg.fork.repository'),
 
                '_method': 'put'
 
            })
 

	
 
            return htmlfill.render(
 
                render('admin/user_groups/user_group_edit.html'),
 
                defaults=defaults,
 
                errors=e,
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during update of user group %s') \
 
                    % request.POST.get('users_group_name'), category='error')
 

	
 
        return redirect(url('edit_users_group', id=id))
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def delete(self, id):
 
        """DELETE /user_groups/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('users_group', id=ID),
 
        #           method='delete')
 
        # url('users_group', id=ID)
 
        usr_gr = UserGroup.get_or_404(id)
 
        try:
 
            UserGroupModel().delete(usr_gr)
 
            Session().commit()
 
            h.flash(_('Successfully deleted user group'), category='success')
 
        except UserGroupsAssignedException, e:
 
            h.flash(e, category='error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during deletion of user group'),
 
                    category='error')
 
        return redirect(url('users_groups'))
 

	
 
    def show(self, id, format='html'):
 
        """GET /user_groups/id: Show a specific item"""
 
        # url('users_group', id=ID)
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def edit(self, id, format='html'):
 
        """GET /user_groups/id/edit: Form to edit an existing item"""
 
        # url('edit_users_group', id=ID)
 

	
 
        c.user_group = UserGroup.get_or_404(id)
 
        c.active = 'settings'
 
        self.__load_data(id)
 

	
 
        defaults = self.__load_defaults(id)
 

	
 
        return htmlfill.render(
 
            render('admin/user_groups/user_group_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def edit_perms(self, id):
 
        c.user_group = UserGroup.get_or_404(id)
 
        c.active = 'perms'
 

	
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.user_groups_array = repo_model.get_user_groups_js()
 

	
 
        defaults = {}
 
        # fill user group users
 
        for p in c.user_group.user_user_group_to_perm:
 
            defaults.update({'u_perm_%s' % p.user.username:
 
                             p.permission.permission_name})
 

	
 
        for p in c.user_group.user_group_user_group_to_perm:
 
            defaults.update({'g_perm_%s' % p.user_group.users_group_name:
 
                             p.permission.permission_name})
 

	
 
        return htmlfill.render(
 
            render('admin/user_groups/user_group_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def update_perms(self, id):
 
        """
 
        grant permission for given usergroup
 

	
 
        :param id:
 
        """
 
        user_group = UserGroup.get_or_404(id)
 
        form = UserGroupPermsForm()().to_python(request.POST)
 

	
 
        # set the permissions !
 
        try:
 
            UserGroupModel()._update_permissions(user_group, form['perms_new'],
 
                                                 form['perms_updates'])
 
        except RepoGroupAssignmentError:
 
            h.flash(_('Target group cannot be the same'), category='error')
 
            return redirect(url('edit_user_group_perms', id=id))
 
        #TODO: implement this
 
        #action_logger(self.authuser, 'admin_changed_repo_permissions',
 
        #              repo_name, self.ip_addr, self.sa)
 
        Session().commit()
 
        h.flash(_('User Group permissions updated'), category='success')
 
        return redirect(url('edit_user_group_perms', id=id))
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def delete_perms(self, id):
 
        """
 
        DELETE an existing repository group permission user
 

	
 
        :param group_name:
 
        """
 
        try:
 
            obj_type = request.POST.get('obj_type')
 
            obj_id = None
 
            if obj_type == 'user':
 
                obj_id = safe_int(request.POST.get('user_id'))
 
            elif obj_type == 'user_group':
 
                obj_id = safe_int(request.POST.get('user_group_id'))
 

	
 
            if not c.authuser.is_admin:
 
                if obj_type == 'user' and c.authuser.user_id == obj_id:
 
                    msg = _('Cannot revoke permission for yourself as admin')
 
                    h.flash(msg, category='warning')
 
                    raise Exception('revoke admin permission on self')
 
            if obj_type == 'user':
 
                UserGroupModel().revoke_user_permission(user_group=id,
 
                                                        user=obj_id)
 
            elif obj_type == 'user_group':
 
                UserGroupModel().revoke_user_group_permission(target_user_group=id,
 
                                                              user_group=obj_id)
 
            Session().commit()
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during revoking of permission'),
 
                    category='error')
 
            raise HTTPInternalServerError()
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def edit_default_perms(self, id):
 
        c.user_group = UserGroup.get_or_404(id)
 
        c.active = 'default_perms'
 

	
 
        permissions = {
 
            'repositories': {},
 
            'repositories_groups': {}
 
        }
 
        ugroup_repo_perms = UserGroupRepoToPerm.query()\
 
            .options(joinedload(UserGroupRepoToPerm.permission))\
 
            .options(joinedload(UserGroupRepoToPerm.repository))\
 
            .filter(UserGroupRepoToPerm.users_group_id == id)\
 
            .all()
 

	
 
        for gr in ugroup_repo_perms:
 
            permissions['repositories'][gr.repository.repo_name]  \
 
                = gr.permission.permission_name
 

	
 
        ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
 
            .options(joinedload(UserGroupRepoGroupToPerm.permission))\
 
            .options(joinedload(UserGroupRepoGroupToPerm.group))\
 
            .filter(UserGroupRepoGroupToPerm.users_group_id == id)\
 
            .all()
 

	
 
        for gr in ugroup_group_perms:
 
            permissions['repositories_groups'][gr.group.group_name] \
 
                = gr.permission.permission_name
 
        c.permissions = permissions
 

	
 
        ug_model = UserGroupModel()
 

	
 
        defaults = c.user_group.get_dict()
 
        defaults.update({
 
            'create_repo_perm': ug_model.has_perm(c.user_group,
 
                                                  'hg.create.repository'),
 
            'create_user_group_perm': ug_model.has_perm(c.user_group,
 
                                                        'hg.usergroup.create.true'),
 
            'fork_repo_perm': ug_model.has_perm(c.user_group,
 
                                                'hg.fork.repository'),
 
        })
 

	
 
        return htmlfill.render(
 
            render('admin/user_groups/user_group_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
 
    def update_default_perms(self, id):
 
        """PUT /users_perm/id: Update an existing item"""
 
        # url('users_group_perm', id=ID, method='put')
 

	
 
        user_group = UserGroup.get_or_404(id)
 

	
kallithea/controllers/admin/users.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.admin.users
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Users crud controller for pylons
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 4, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
import formencode
 
from pylons import response
 

	
 
from formencode import htmlfill
 
from pylons import request, session, tmpl_context as c, url, config
 
from pylons.controllers.util import redirect
 
from pylons.i18n.translation import _
 
from sqlalchemy.sql.expression import func
 

	
 
import kallithea
 
from kallithea.lib.exceptions import DefaultUserException, \
 
    UserOwnsReposException, UserCreationError
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
 
    AuthUser, generate_api_key
 
import kallithea.lib.auth_modules.auth_internal
 
from kallithea.lib import auth_modules
 
from kallithea.lib.base import BaseController, render
 
from kallithea.model.api_key import ApiKeyModel
 

	
 
from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
 
from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
 
from kallithea.model.user import UserModel
 
from kallithea.model.meta import Session
 
from kallithea.lib.utils import action_logger
 
from kallithea.lib.compat import json
 
from kallithea.lib.utils2 import datetime_to_time, str2bool, safe_int
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class UsersController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    @LoginRequired()
 
    @HasPermissionAllDecorator('hg.admin')
 
    def __before__(self):
 
        super(UsersController, self).__before__()
 
        c.available_permissions = config['available_permissions']
 

	
 
    def index(self, format='html'):
 
        """GET /users: All items in the collection"""
 
        # url('users')
 

	
 
        c.users_list = User.query().order_by(User.username)\
 
                        .filter(User.username != User.DEFAULT_USER)\
 
                        .order_by(func.lower(User.username))\
 
                        .all()
 

	
 
        users_data = []
 
        total_records = len(c.users_list)
 
        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        grav_tmpl = lambda user_email, size: (
 
                template.get_def("user_gravatar")
 
                .render(user_email, size, _=_, h=h, c=c))
 

	
 
        username = lambda user_id, username: (
 
                template.get_def("user_name")
 
                .render(user_id, username, _=_, h=h, c=c))
 

	
 
        user_actions = lambda user_id, username: (
 
                template.get_def("user_actions")
 
                .render(user_id, username, _=_, h=h, c=c))
 

	
 
        for user in c.users_list:
 

	
 
            users_data.append({
 
                "gravatar": grav_tmpl(user. email, 20),
 
                "raw_name": user.username,
 
                "username": username(user.user_id, user.username),
 
                "firstname": user.name,
 
                "lastname": user.lastname,
 
                "last_login": h.fmt_date(user.last_login),
 
                "last_login_raw": datetime_to_time(user.last_login),
 
                "active": h.boolicon(user.active),
 
                "admin": h.boolicon(user.admin),
 
                "extern_type": user.extern_type,
 
                "extern_name": user.extern_name,
 
                "action": user_actions(user.user_id, user.username),
 
            })
 

	
 
        c.data = json.dumps({
 
            "totalRecords": total_records,
 
            "startIndex": 0,
 
            "sort": None,
 
            "dir": "asc",
 
            "records": users_data
 
        })
 

	
 
        return render('admin/users/users.html')
 

	
 
    def create(self):
 
        """POST /users: Create a new item"""
 
        # url('users')
 
        c.default_extern_type = auth_modules.auth_internal.RhodeCodeAuthPlugin.name
 
        user_model = UserModel()
 
        user_form = UserForm()()
 
        try:
 
            form_result = user_form.to_python(dict(request.POST))
 
            user_model.create(form_result)
 
            usr = form_result['username']
 
            action_logger(self.authuser, 'admin_created_user:%s' % usr,
 
                          None, self.ip_addr, self.sa)
 
            h.flash(_('Created user %s') % usr,
 
                    category='success')
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            return htmlfill.render(
 
                render('admin/users/user_add.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except UserCreationError, e:
 
            h.flash(e, 'error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during creation of user %s') \
 
                    % request.POST.get('username'), category='error')
 
        return redirect(url('users'))
 

	
 
    def new(self, format='html'):
 
        """GET /users/new: Form to create a new item"""
 
        # url('new_user')
 
        c.default_extern_type = auth_modules.auth_internal.RhodeCodeAuthPlugin.name
 
        return render('admin/users/user_add.html')
 

	
 
    def update(self, id):
 
        """PUT /users/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('update_user', id=ID),
 
        #           method='put')
 
        # url('user', id=ID)
 
        c.active = 'profile'
 
        user_model = UserModel()
 
        c.user = user_model.get(id)
 
        c.extern_type = c.user.extern_type
 
        c.extern_name = c.user.extern_name
 
        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
 
        _form = UserForm(edit=True, old_data={'user_id': id,
 
                                              'email': c.user.email})()
 
        form_result = {}
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            skip_attrs = ['extern_type', 'extern_name']
 
            #TODO: plugin should define if username can be updated
 
            if c.extern_type != "internal":
 
                # forbid updating username for external accounts
 
                skip_attrs.append('username')
 

	
 
            user_model.update(id, form_result, skip_attrs=skip_attrs)
 
            usr = form_result['username']
 
            action_logger(self.authuser, 'admin_updated_user:%s' % usr,
 
                          None, self.ip_addr, self.sa)
 
            h.flash(_('User updated successfully'), category='success')
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            defaults = errors.value
 
            e = errors.error_dict or {}
 
            defaults.update({
 
                'create_repo_perm': user_model.has_perm(id,
 
                                                        'hg.create.repository'),
 
                'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
 
                '_method': 'put'
 
            })
 
            return htmlfill.render(
 
                render('admin/users/user_edit.html'),
 
                defaults=defaults,
 
                errors=e,
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during update of user %s') \
 
                    % form_result.get('username'), category='error')
 
        return redirect(url('edit_user', id=id))
 

	
 
    def delete(self, id):
 
        """DELETE /users/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('delete_user', id=ID),
 
        #           method='delete')
 
        # url('user', id=ID)
 
        usr = User.get_or_404(id)
 
        try:
 
            UserModel().delete(usr)
 
            Session().commit()
 
            h.flash(_('Successfully deleted user'), category='success')
 
        except (UserOwnsReposException, DefaultUserException), e:
 
            h.flash(e, category='warning')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during deletion of user'),
 
                    category='error')
 
        return redirect(url('users'))
 

	
 
    def show(self, id, format='html'):
 
        """GET /users/id: Show a specific item"""
 
        # url('user', id=ID)
 
        User.get_or_404(-1)
 

	
 
    def edit(self, id, format='html'):
 
        """GET /users/id/edit: Form to edit an existing item"""
 
        # url('edit_user', id=ID)
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
 
            return redirect(url('users'))
 

	
 
        c.active = 'profile'
 
        c.extern_type = c.user.extern_type
 
        c.extern_name = c.user.extern_name
 
        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
 

	
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def edit_advanced(self, id):
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
 
            return redirect(url('users'))
 

	
 
        c.active = 'advanced'
 
        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
 

	
 
        umodel = UserModel()
 
        defaults = c.user.get_dict()
 
        defaults.update({
 
            'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
 
            'create_user_group_perm': umodel.has_perm(c.user,
 
                                                      'hg.usergroup.create.true'),
 
            'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
 
        })
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def edit_api_keys(self, id):
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
 
            return redirect(url('users'))
 

	
 
        c.active = 'api_keys'
 
        show_expired = True
 
        c.lifetime_values = [
 
            (str(-1), _('forever')),
 
            (str(5), _('5 minutes')),
 
            (str(60), _('1 hour')),
 
            (str(60 * 24), _('1 day')),
 
            (str(60 * 24 * 30), _('1 month')),
 
        ]
 
        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 
        c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
 
                                                     show_expired=show_expired)
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def add_api_key(self, id):
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
 
            return redirect(url('users'))
 

	
 
        lifetime = safe_int(request.POST.get('lifetime'), -1)
 
        description = request.POST.get('description')
 
        new_api_key = ApiKeyModel().create(c.user.user_id, description, lifetime)
 
        Session().commit()
 
        h.flash(_("Api key successfully created"), category='success')
 
        return redirect(url('edit_user_api_keys', id=c.user.user_id))
 

	
 
    def delete_api_key(self, id):
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
 
            return redirect(url('users'))
 

	
 
        api_key = request.POST.get('del_api_key')
 
        if request.POST.get('del_api_key_builtin'):
 
            user = User.get(c.user.user_id)
 
            if user:
 
                user.api_key = generate_api_key(user.username)
 
                Session().add(user)
 
                Session().commit()
 
                h.flash(_("Api key successfully reset"), category='success')
 
        elif api_key:
 
            ApiKeyModel().delete(api_key, c.user.user_id)
 
            Session().commit()
 
            h.flash(_("Api key successfully deleted"), category='success')
 

	
 
        return redirect(url('edit_user_api_keys', id=c.user.user_id))
 

	
 
    def update_account(self, id):
 
        pass
 

	
 
    def edit_perms(self, id):
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
 
            return redirect(url('users'))
 

	
 
        c.active = 'perms'
 
        c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
 

	
 
        umodel = UserModel()
 
        defaults = c.user.get_dict()
 
        defaults.update({
 
            'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
 
            'create_user_group_perm': umodel.has_perm(c.user,
 
                                                      'hg.usergroup.create.true'),
 
            'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
 
        })
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def update_perms(self, id):
 
        """PUT /users_perm/id: Update an existing item"""
 
        # url('user_perm', id=ID, method='put')
 
        user = User.get_or_404(id)
 

	
 
        try:
 
            form = CustomDefaultPermissionsForm()()
 
            form_result = form.to_python(request.POST)
 

	
 
            inherit_perms = form_result['inherit_default_permissions']
 
            user.inherit_default_permissions = inherit_perms
 
            Session().add(user)
 
            user_model = UserModel()
 

	
 
            defs = UserToPerm.query()\
 
                .filter(UserToPerm.user == user)\
 
                .all()
 
            for ug in defs:
 
                Session().delete(ug)
 

	
 
            if form_result['create_repo_perm']:
 
                user_model.grant_perm(id, 'hg.create.repository')
 
            else:
 
                user_model.grant_perm(id, 'hg.create.none')
 
            if form_result['create_user_group_perm']:
 
                user_model.grant_perm(id, 'hg.usergroup.create.true')
 
            else:
 
                user_model.grant_perm(id, 'hg.usergroup.create.false')
 
            if form_result['fork_repo_perm']:
 
                user_model.grant_perm(id, 'hg.fork.repository')
 
            else:
 
                user_model.grant_perm(id, 'hg.fork.none')
 
            h.flash(_("Updated permissions"), category='success')
 
            Session().commit()
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during permissions saving'),
 
                    category='error')
 
        return redirect(url('edit_user_perms', id=id))
 

	
 
    def edit_emails(self, id):
 
        c.user = User.get_or_404(id)
 
        if c.user.username == User.DEFAULT_USER:
 
            h.flash(_("You can't edit this user"), category='warning')
kallithea/controllers/api/__init__.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.api
 
~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
JSON RPC controller
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Aug 20, 2011
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import inspect
 
import logging
 
import types
 
import urllib
 
import traceback
 
import time
 

	
 
from paste.response import replace_header
 
from pylons.controllers import WSGIController
 

	
 
from webob.exc import HTTPError
 

	
 
from kallithea.model.db import User
 
from kallithea.model import meta
 
from kallithea.lib.compat import izip_longest, json
 
from kallithea.lib.auth import AuthUser
 
from kallithea.lib.base import _get_ip_addr as _get_ip, _get_access_path
 
from kallithea.lib.utils2 import safe_unicode, safe_str
 

	
 
log = logging.getLogger('JSONRPC')
 

	
 

	
 
class JSONRPCError(BaseException):
 

	
 
    def __init__(self, message):
 
        self.message = message
 
        super(JSONRPCError, self).__init__()
 

	
 
    def __str__(self):
 
        return safe_str(self.message)
 

	
 

	
 
def jsonrpc_error(message, retid=None, code=None):
 
    """
 
    Generate a Response object with a JSON-RPC error body
 

	
 
    :param code:
 
    :param retid:
 
    :param message:
 
    """
 
    from pylons.controllers.util import Response
 
    return Response(
 
        body=json.dumps(dict(id=retid, result=None, error=message)),
 
        status=code,
 
        content_type='application/json'
 
    )
 

	
 

	
 
class JSONRPCController(WSGIController):
 
    """
 
     A WSGI-speaking JSON-RPC controller class
 

	
 
     See the specification:
 
     <http://json-rpc.org/wiki/specification>`.
 

	
 
     Valid controller return values should be json-serializable objects.
 

	
 
     Sub-classes should catch their exceptions and raise JSONRPCError
 
     if they want to pass meaningful errors to the client.
 

	
 
     """
 

	
 
    def _get_ip_addr(self, environ):
 
        return _get_ip(environ)
 

	
 
    def _get_method_args(self):
 
        """
 
        Return `self._rpc_args` to dispatched controller method
 
        chosen by __call__
 
        """
 
        return self._rpc_args
 

	
 
    def __call__(self, environ, start_response):
 
        """
 
        Parse the request body as JSON, look up the method on the
 
        controller and if it exists, dispatch to it.
 
        """
 
        try:
 
            return self._handle_request(environ, start_response)
 
        finally:
 
            meta.Session.remove()
 

	
 
    def _handle_request(self, environ, start_response):
 
        start = time.time()
 
        ip_addr = self.ip_addr = self._get_ip_addr(environ)
 
        self._req_id = None
 
        if 'CONTENT_LENGTH' not in environ:
 
            log.debug("No Content-Length")
 
            return jsonrpc_error(retid=self._req_id,
 
                                 message="No Content-Length in request")
 
        else:
 
            length = environ['CONTENT_LENGTH'] or 0
 
            length = int(environ['CONTENT_LENGTH'])
 
            log.debug('Content-Length: %s' % length)
 

	
 
        if length == 0:
 
            log.debug("Content-Length is 0")
 
            return jsonrpc_error(retid=self._req_id,
 
                                 message="Content-Length is 0")
 

	
 
        raw_body = environ['wsgi.input'].read(length)
 

	
 
        try:
 
            json_body = json.loads(raw_body)
 
        except ValueError, e:
 
            # catch JSON errors Here
 
            return jsonrpc_error(retid=self._req_id,
 
                                 message="JSON parse error ERR:%s RAW:%r"
 
                                 % (e, raw_body))
 

	
 
        # check AUTH based on API KEY
 
        try:
 
            self._req_api_key = json_body['api_key']
 
            self._req_id = json_body['id']
 
            self._req_method = json_body['method']
 
            self._request_params = json_body['args']
 
            if not isinstance(self._request_params, dict):
 
                self._request_params = {}
 

	
 
            log.debug(
 
                'method: %s, params: %s' % (self._req_method,
 
                                            self._request_params)
 
            )
 
        except KeyError, e:
 
            return jsonrpc_error(retid=self._req_id,
 
                                 message='Incorrect JSON query missing %s' % e)
 

	
 
        # check if we can find this session using api_key
 
        try:
 
            u = User.get_by_api_key(self._req_api_key)
 
            if u is None:
 
                return jsonrpc_error(retid=self._req_id,
 
                                     message='Invalid API KEY')
 

	
 
            #check if we are allowed to use this IP
 
            auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
 
            if not auth_u.ip_allowed:
 
                return jsonrpc_error(retid=self._req_id,
 
                        message='request from IP:%s not allowed' % (ip_addr,))
 
            else:
 
                log.info('Access for IP:%s allowed' % (ip_addr,))
 

	
 
        except Exception, e:
 
            return jsonrpc_error(retid=self._req_id,
 
                                 message='Invalid API KEY')
 

	
 
        self._error = None
 
        try:
 
            self._func = self._find_method()
 
        except AttributeError, e:
 
            return jsonrpc_error(retid=self._req_id,
 
                                 message=str(e))
 

	
 
        # now that we have a method, add self._req_params to
 
        # self.kargs and dispatch control to WGIController
 
        argspec = inspect.getargspec(self._func)
 
        arglist = argspec[0][1:]
 
        defaults = map(type, argspec[3] or [])
 
        default_empty = types.NotImplementedType
 

	
 
        # kw arguments required by this method
 
        func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
 
                                        fillvalue=default_empty))
 

	
 
        # this is little trick to inject logged in user for
 
        # perms decorators to work they expect the controller class to have
 
        # authuser attribute set
 
        self.authuser = auth_u
 

	
 
        # This attribute will need to be first param of a method that uses
 
        # api_key, which is translated to instance of user at that name
 
        USER_SESSION_ATTR = 'apiuser'
 

	
 
        if USER_SESSION_ATTR not in arglist:
 
            return jsonrpc_error(
 
                retid=self._req_id,
 
                message='This method [%s] does not support '
 
                         'authentication (missing %s param)' % (
 
                                    self._func.__name__, USER_SESSION_ATTR)
 
            )
 

	
 
        # get our arglist and check if we provided them as args
 
        for arg, default in func_kwargs.iteritems():
 
            if arg == USER_SESSION_ATTR:
 
                # USER_SESSION_ATTR is something translated from api key and
 
                # this is checked before so we don't need validate it
 
                continue
 

	
 
            # skip the required param check if it's default value is
 
            # NotImplementedType (default_empty)
 
            if default == default_empty and arg not in self._request_params:
 
                return jsonrpc_error(
 
                    retid=self._req_id,
 
                    message=(
 
                        'Missing non optional `%s` arg in JSON DATA' % arg
 
                    )
 
                )
 

	
 
        self._rpc_args = {USER_SESSION_ATTR: u}
 

	
 
        self._rpc_args.update(self._request_params)
 

	
 
        self._rpc_args['action'] = self._req_method
 
        self._rpc_args['environ'] = environ
 
        self._rpc_args['start_response'] = start_response
 

	
 
        status = []
 
        headers = []
 
        exc_info = []
 

	
 
        def change_content(new_status, new_headers, new_exc_info=None):
 
            status.append(new_status)
 
            headers.extend(new_headers)
 
            exc_info.append(new_exc_info)
 

	
 
        output = WSGIController.__call__(self, environ, change_content)
 
        output = list(output)
 
        headers.append(('Content-Length', str(len(output[0]))))
 
        replace_header(headers, 'Content-Type', 'application/json')
 
        start_response(status[0], headers, exc_info[0])
 
        log.info('IP: %s Request to %s time: %.3fs' % (
 
            self._get_ip_addr(environ),
 
            safe_unicode(_get_access_path(environ)), time.time() - start)
 
        )
 
        return output
 

	
 
    def _dispatch_call(self):
 
        """
 
        Implement dispatch interface specified by WSGIController
 
        """
 
        raw_response = ''
 
        try:
 
            raw_response = self._inspect_call(self._func)
 
            if isinstance(raw_response, HTTPError):
 
                self._error = str(raw_response)
 
        except JSONRPCError, e:
 
            self._error = safe_str(e)
 
        except Exception, e:
 
            log.error('Encountered unhandled exception: %s'
 
                      % (traceback.format_exc(),))
 
            json_exc = JSONRPCError('Internal server error')
 
            self._error = safe_str(json_exc)
 

	
 
        if self._error is not None:
 
            raw_response = None
 

	
 
        response = dict(id=self._req_id, result=raw_response, error=self._error)
 
        try:
 
            return json.dumps(response)
 
        except TypeError, e:
 
            log.error('API FAILED. Error encoding response: %s' % e)
 
            return json.dumps(
 
                dict(
 
                    id=self._req_id,
 
                    result=None,
 
                    error="Error encoding response"
 
                )
 
            )
 

	
 
    def _find_method(self):
 
        """
 
        Return method named by `self._req_method` in controller if able
 
        """
 
        log.debug('Trying to find JSON-RPC method: %s' % (self._req_method,))
 
        if self._req_method.startswith('_'):
 
            raise AttributeError("Method not allowed")
 

	
 
        try:
 
            func = getattr(self, self._req_method, None)
 
        except UnicodeEncodeError:
 
            raise AttributeError("Problem decoding unicode in requested "
 
                                 "method name.")
 

	
 
        if isinstance(func, types.MethodType):
 
            return func
 
        else:
 
            raise AttributeError("No such method: %s" % (self._req_method,))
kallithea/controllers/api/api.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.api
 
~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
API controller for RhodeCode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Aug 20, 2011
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
import time
 
import traceback
 
import logging
 
from sqlalchemy import or_
 

	
 
from kallithea.controllers.api import JSONRPCController, JSONRPCError
 
from kallithea.lib.auth import (
 
    PasswordGenerator, AuthUser, HasPermissionAllDecorator,
 
    HasPermissionAnyDecorator, HasPermissionAnyApi, HasRepoPermissionAnyApi,
 
    HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny)
 
from kallithea.lib.utils import map_groups, repo2db_mapper
 
from kallithea.lib.utils2 import (
 
    str2bool, time_to_datetime, safe_int, Optional, OAttr)
 
from kallithea.model.meta import Session
 
from kallithea.model.repo_group import RepoGroupModel
 
from kallithea.model.scm import ScmModel, UserGroupList
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.user import UserModel
 
from kallithea.model.user_group import UserGroupModel
 
from kallithea.model.gist import GistModel
 
from kallithea.model.db import (
 
    Repository, Setting, UserIpMap, Permission, User, Gist,
 
    RepoGroup)
 
from kallithea.lib.compat import json
 
from kallithea.lib.exceptions import (
 
    DefaultUserException, UserGroupsAssignedException)
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def store_update(updates, attr, name):
 
    """
 
    Stores param in updates dict if it's not instance of Optional
 
    allows easy updates of passed in params
 
    """
 
    if not isinstance(attr, Optional):
 
        updates[name] = attr
 

	
 

	
 
def get_user_or_error(userid):
 
    """
 
    Get user by id or name or return JsonRPCError if not found
 

	
 
    :param userid:
 
    """
 
    user = UserModel().get_user(userid)
 
    if user is None:
 
        raise JSONRPCError("user `%s` does not exist" % (userid,))
 
    return user
 

	
 

	
 
def get_repo_or_error(repoid):
 
    """
 
    Get repo by id or name or return JsonRPCError if not found
 

	
 
    :param repoid:
 
    """
 
    repo = RepoModel().get_repo(repoid)
 
    if repo is None:
 
        raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
    return repo
 

	
 

	
 
def get_repo_group_or_error(repogroupid):
 
    """
 
    Get repo group by id or name or return JsonRPCError if not found
 

	
 
    :param repogroupid:
 
    """
 
    repo_group = RepoGroupModel()._get_repo_group(repogroupid)
 
    if repo_group is None:
 
        raise JSONRPCError(
 
            'repository group `%s` does not exist' % (repogroupid,))
 
    return repo_group
 

	
 

	
 
def get_user_group_or_error(usergroupid):
 
    """
 
    Get user group by id or name or return JsonRPCError if not found
 

	
 
    :param usergroupid:
 
    """
 
    user_group = UserGroupModel().get_group(usergroupid)
 
    if user_group is None:
 
        raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
    return user_group
 

	
 

	
 
def get_perm_or_error(permid, prefix=None):
 
    """
 
    Get permission by id or name or return JsonRPCError if not found
 

	
 
    :param permid:
 
    """
 
    perm = Permission.get_by_key(permid)
 
    if perm is None:
 
        raise JSONRPCError('permission `%s` does not exist' % (permid,))
 
    if prefix:
 
        if not perm.permission_name.startswith(prefix):
 
            raise JSONRPCError('permission `%s` is invalid, '
 
                               'should start with %s' % (permid, prefix))
 
    return perm
 

	
 

	
 
def get_gist_or_error(gistid):
 
    """
 
    Get gist by id or gist_access_id or return JsonRPCError if not found
 

	
 
    :param gistid:
 
    """
 
    gist = GistModel().get_gist(gistid)
 
    if gist is None:
 
        raise JSONRPCError('gist `%s` does not exist' % (gistid,))
 
    return gist
 

	
 

	
 
class ApiController(JSONRPCController):
 
    """
 
    API Controller
 

	
 
    Each method takes USER as first argument. This is then, based on given
 
    API_KEY propagated as instance of user object who's making the call.
 

	
 
    example function::
 

	
 
        def func(apiuser,arg1, arg2,...):
 
            pass
 

	
 
    Each function should also **raise** JSONRPCError for any
 
    errors that happens.
 

	
 
    """
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def test(self, apiuser, args):
 
        return args
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def pull(self, apiuser, repoid):
 
        """
 
        Triggers a pull from remote location on given repo. Can be used to
 
        automatically keep remote repos up to date. This command can be executed
 
        only using api_key belonging to user with admin rights
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 

	
 
        OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : {
 
            "msg": "Pulled from `<repository name>`"
 
            "repository": "<repository name>"
 
          }
 
          error :  null
 

	
 
        ERROR OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : null
 
          error :  {
 
            "Unable to pull changes from `<reponame>`"
 
          }
 

	
 
        """
 

	
 
        repo = get_repo_or_error(repoid)
 

	
 
        try:
 
            ScmModel().pull_changes(repo.repo_name,
 
                                    self.authuser.username)
 
            return dict(
 
                msg='Pulled from `%s`' % repo.repo_name,
 
                repository=repo.repo_name
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'Unable to pull changes from `%s`' % repo.repo_name
 
            )
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
 
        """
 
        Triggers rescan repositories action. If remove_obsolete is set
 
        than also delete repos that are in database but not in the filesystem.
 
        aka "clean zombies". This command can be executed only using api_key
 
        belonging to user with admin rights.
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param remove_obsolete: deletes repositories from
 
            database that are not found on the filesystem
 
        :type remove_obsolete: Optional(bool)
 

	
 
        OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : {
 
            'added': [<added repository name>,...]
 
            'removed': [<removed repository name>,...]
 
          }
 
          error :  null
 

	
 
        ERROR OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : null
 
          error :  {
 
            'Error occurred during rescan repositories action'
 
          }
 

	
 
        """
 

	
 
        try:
 
            rm_obsolete = Optional.extract(remove_obsolete)
 
            added, removed = repo2db_mapper(ScmModel().repo_scan(),
 
                                            remove_obsolete=rm_obsolete)
 
            return {'added': added, 'removed': removed}
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'Error occurred during rescan repositories action'
 
            )
 

	
 
    def invalidate_cache(self, apiuser, repoid):
 
        """
 
        Invalidate cache for repository.
 
        This command can be executed only using api_key belonging to user with admin
 
        rights or regular user that have write or admin or write access to repository.
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 

	
 
        OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : {
 
            'msg': Cache for repository `<repository name>` was invalidated,
 
            'repository': <repository name>
 
          }
 
          error :  null
 

	
 
        ERROR OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : null
 
          error :  {
 
            'Error occurred during cache invalidation action'
 
          }
 

	
 
        """
 
        repo = get_repo_or_error(repoid)
 
        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            # check if we have admin permission for this repo !
 
            if not HasRepoPermissionAnyApi('repository.admin',
 
                                           'repository.write')(
 
                    user=apiuser, repo_name=repo.repo_name):
 
                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 

	
 
        try:
 
            ScmModel().mark_for_invalidation(repo.repo_name)
 
            return dict(
 
                msg='Cache for repository `%s` was invalidated' % (repoid,),
 
                repository=repo.repo_name
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'Error occurred during cache invalidation action'
 
            )
 

	
 
    # permission check inside
 
    def lock(self, apiuser, repoid, locked=Optional(None),
 
             userid=Optional(OAttr('apiuser'))):
 
        """
 
        Set locking state on given repository by given user. If userid param
 
        is skipped, then it is set to id of user whos calling this method.
 
        If locked param is skipped then function shows current lock state of
 
        given repo. This command can be executed only using api_key belonging
 
        to user with admin rights or regular user that have admin or write
 
        access to repository.
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 
        :param locked: lock state to be set
 
        :type locked: Optional(bool)
 
        :param userid: set lock as user
 
        :type userid: Optional(str or int)
 

	
 
        OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : {
 
            'repo': '<reponame>',
 
            'locked': <bool: lock state>,
 
            'locked_since': <int: lock timestamp>,
 
            'locked_by': <username of person who made the lock>,
 
            'lock_state_changed': <bool: True if lock state has been changed in this request>,
 
            'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
 
            or
 
            'msg': 'Repo `<repository name>` not locked.'
 
            or
 
            'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
 
          }
 
          error :  null
 

	
 
        ERROR OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : null
 
          error :  {
 
            'Error occurred locking repository `<reponame>`
 
          }
 

	
 
        """
 
        repo = get_repo_or_error(repoid)
 
        if HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            pass
 
        elif HasRepoPermissionAnyApi('repository.admin',
 
                                     'repository.write')(user=apiuser,
 
                                                         repo_name=repo.repo_name):
 
            # make sure normal user does not pass someone else userid,
 
            # he is not allowed to do that
 
            if not isinstance(userid, Optional) and userid != apiuser.user_id:
 
                raise JSONRPCError(
 
                    'userid is not the same as your user'
 
                )
 
        else:
 
            raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 

	
 
        if isinstance(userid, Optional):
 
            userid = apiuser.user_id
 

	
 
        user = get_user_or_error(userid)
 

	
 
        if isinstance(locked, Optional):
 
            lockobj = Repository.getlock(repo)
 

	
 
            if lockobj[0] is None:
 
                _d = {
 
                    'repo': repo.repo_name,
 
                    'locked': False,
 
                    'locked_since': None,
 
                    'locked_by': None,
 
                    'lock_state_changed': False,
 
                    'msg': 'Repo `%s` not locked.' % repo.repo_name
 
                }
 
                return _d
 
            else:
 
                userid, time_ = lockobj
 
                lock_user = get_user_or_error(userid)
 
                _d = {
 
                    'repo': repo.repo_name,
 
                    'locked': True,
 
                    'locked_since': time_,
 
                    'locked_by': lock_user.username,
 
                    'lock_state_changed': False,
 
                    'msg': ('Repo `%s` locked by `%s` on `%s`.'
 
                            % (repo.repo_name, lock_user.username,
 
                               json.dumps(time_to_datetime(time_))))
 
                }
 
                return _d
 

	
 
        # force locked state through a flag
 
        else:
 
            locked = str2bool(locked)
 
            try:
 
                if locked:
 
                    lock_time = time.time()
 
                    Repository.lock(repo, user.user_id, lock_time)
 
                else:
 
                    lock_time = None
 
                    Repository.unlock(repo)
kallithea/controllers/bookmarks.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.bookmarks
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Bookmarks controller for rhodecode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Dec 1, 2011
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 

	
 
from pylons import tmpl_context as c
 

	
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.compat import OrderedDict
 
from webob.exc import HTTPNotFound
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class BookmarksController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(BookmarksController, self).__before__()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self):
 
        if c.db_repo_scm_instance.alias != 'hg':
 
            raise HTTPNotFound()
 

	
 
        c.repo_bookmarks = OrderedDict()
 

	
 
        bookmarks = [(name, c.db_repo_scm_instance.get_changeset(hash_)) for \
 
                 name, hash_ in c.db_repo_scm_instance._repo._bookmarks.items()]
 
        ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
 
        for name, cs_book in ordered_tags:
 
            c.repo_bookmarks[name] = cs_book
 

	
 
        return render('bookmarks/bookmarks.html')
kallithea/controllers/branches.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.branches
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
branches controller for rhodecode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 21, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import binascii
 

	
 
from pylons import tmpl_context as c
 

	
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.compat import OrderedDict
 
from kallithea.lib.utils2 import safe_unicode
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class BranchesController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(BranchesController, self).__before__()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self):
 

	
 
        def _branchtags(localrepo):
 
            bt_closed = {}
 
            for bn, heads in localrepo.branchmap().iteritems():
 
                tip = heads[-1]
 
                if 'close' in localrepo.changelog.read(tip)[5]:
 
                    bt_closed[bn] = tip
 
            return bt_closed
 

	
 
        cs_g = c.db_repo_scm_instance.get_changeset
 

	
 
        c.repo_closed_branches = {}
 
        if c.db_repo.repo_type == 'hg':
 
            bt_closed = _branchtags(c.db_repo_scm_instance._repo)
 
            _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
 
                                for n, h in bt_closed.items()]
 

	
 
            c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
 
                                                    key=lambda ctx: ctx[0],
 
                                                    reverse=False))
 

	
 
        _branches = [(safe_unicode(n), cs_g(h))
 
                     for n, h in c.db_repo_scm_instance.branches.items()]
 
        c.repo_branches = OrderedDict(sorted(_branches,
 
                                             key=lambda ctx: ctx[0],
 
                                             reverse=False))
 

	
 
        return render('branches/branches.html')
kallithea/controllers/changelog.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.changelog
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
changelog controller for rhodecode
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 21, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 

	
 
from pylons import request, url, session, tmpl_context as c
 
from pylons.controllers.util import redirect
 
from pylons.i18n.translation import _
 
from webob.exc import HTTPNotFound, HTTPBadRequest
 

	
 
import kallithea.lib.helpers as h
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.helpers import RepoPage
 
from kallithea.lib.compat import json
 
from kallithea.lib.graphmod import _colored, _dagwalker
 
from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\
 
    ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
 
from kallithea.lib.utils2 import safe_int, safe_str
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _load_changelog_summary():
 
    p = safe_int(request.GET.get('page'), 1)
 
    size = safe_int(request.GET.get('size'), 10)
 

	
 
    def url_generator(**kw):
 
        return url('changelog_summary_home',
 
                   repo_name=c.db_repo.repo_name, size=size, **kw)
 

	
 
    collection = c.db_repo_scm_instance
 

	
 
    c.repo_changesets = RepoPage(collection, page=p,
 
                                 items_per_page=size,
 
                                 url=url_generator)
 
    page_revisions = [x.raw_id for x in list(c.repo_changesets)]
 
    c.comments = c.db_repo.get_comments(page_revisions)
 
    c.statuses = c.db_repo.statuses(page_revisions)
 

	
 

	
 
class ChangelogController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(ChangelogController, self).__before__()
 
        c.affected_files_cut_off = 60
 

	
 
    def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
 
                             partial=False):
 
        """
 
        Safe way to get changeset if error occur it redirects to changeset with
 
        proper message. If partial is set then don't do redirect raise Exception
 
        instead
 

	
 
        :param rev: revision to fetch
 
        :param repo: repo instance
 
        """
 

	
 
        try:
 
            return c.db_repo_scm_instance.get_changeset(rev)
 
        except EmptyRepositoryError, e:
 
            if not redirect_after:
 
                return None
 
            h.flash(h.literal(_('There are no changesets yet')),
 
                    category='warning')
 
            redirect(url('changelog_home', repo_name=repo.repo_name))
 

	
 
        except RepositoryError, e:
 
            log.error(traceback.format_exc())
 
            h.flash(safe_str(e), category='warning')
 
            if not partial:
 
                redirect(h.url('changelog_home', repo_name=repo.repo_name))
 
            raise HTTPBadRequest()
 

	
 
    def _graph(self, repo, revs_int, repo_size, size, p):
 
        """
 
        Generates a DAG graph for repo
 

	
 
        :param repo:
 
        :param revs_int:
 
        :param repo_size:
 
        :param size:
 
        :param p:
 
        """
 
        if not revs_int:
 
            c.jsdata = json.dumps([])
 
            return
 

	
 
        data = []
 
        revs = revs_int
 

	
 
        dag = _dagwalker(repo, revs, repo.alias)
 
        dag = _colored(dag)
 
        for (_id, _type, ctx, vtx, edges) in dag:
 
            data.append(['', vtx, edges])
 

	
 
        c.jsdata = json.dumps(data)
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, repo_name, revision=None, f_path=None):
 
        limit = 100
 
        default = 20
 
        if request.GET.get('size'):
 
            c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
 
            session['changelog_size'] = c.size
 
            session.save()
 
        else:
 
            c.size = int(session.get('changelog_size', default))
 
        # min size must be 1
 
        c.size = max(c.size, 1)
 
        p = safe_int(request.GET.get('page', 1), 1)
 
        branch_name = request.GET.get('branch', None)
 
        if (branch_name and
 
            branch_name not in c.db_repo_scm_instance.branches and
 
            branch_name not in c.db_repo_scm_instance.closed_branches and
 
            not revision):
 
            return redirect(url('changelog_file_home', repo_name=c.repo_name,
 
                                    revision=branch_name, f_path=f_path or ''))
 

	
 
        c.changelog_for_path = f_path
 
        try:
 

	
 
            if f_path:
 
                log.debug('generating changelog for path %s' % f_path)
 
                # get the history for the file !
 
                tip_cs = c.db_repo_scm_instance.get_changeset()
 
                try:
 
                    collection = tip_cs.get_file_history(f_path)
 
                except (NodeDoesNotExistError, ChangesetError):
 
                    #this node is not present at tip !
 
                    try:
 
                        cs = self.__get_cs_or_redirect(revision, repo_name)
 
                        collection = cs.get_file_history(f_path)
 
                    except RepositoryError, e:
 
                        h.flash(safe_str(e), category='warning')
 
                        redirect(h.url('changelog_home', repo_name=repo_name))
 
                collection = list(reversed(collection))
 
            else:
 
                collection = c.db_repo_scm_instance.get_changesets(start=0,
 
                                                        branch_name=branch_name)
 
            c.total_cs = len(collection)
 

	
 
            c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
 
                                    items_per_page=c.size, branch=branch_name,)
 
            collection = list(c.pagination)
 
            page_revisions = [x.raw_id for x in c.pagination]
 
            c.comments = c.db_repo.get_comments(page_revisions)
 
            c.statuses = c.db_repo.statuses(page_revisions)
 
        except (EmptyRepositoryError), e:
 
            h.flash(safe_str(e), category='warning')
 
            return redirect(url('summary_home', repo_name=c.repo_name))
 
        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
 
            log.error(traceback.format_exc())
 
            h.flash(safe_str(e), category='error')
 
            return redirect(url('changelog_home', repo_name=c.repo_name))
 

	
 
        c.branch_name = branch_name
 
        c.branch_filters = [('', _('All Branches'))] + \
 
            [(k, k) for k in c.db_repo_scm_instance.branches.keys()]
 
        if c.db_repo_scm_instance.closed_branches:
 
            prefix = _('(closed)') + ' '
 
            c.branch_filters += [('-', '-')] + \
 
                [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
 
        _revs = []
 
        if not f_path:
 
            _revs = [x.revision for x in c.pagination]
 
        self._graph(c.db_repo_scm_instance, _revs, c.total_cs, c.size, p)
 

	
 
        return render('changelog/changelog.html')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changelog_details(self, cs):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            c.cs = c.db_repo_scm_instance.get_changeset(cs)
 
            return render('changelog/changelog_details.html')
 
        raise HTTPNotFound()
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changelog_summary(self, repo_name):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            _load_changelog_summary()
 

	
 
            return render('changelog/changelog_summary_data.html')
 
        raise HTTPNotFound()
kallithea/controllers/changeset.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.changeset
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
changeset controller for pylons showoing changes beetween
 
revisions
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 25, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 
from collections import defaultdict
 
from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
 

	
 
from pylons import tmpl_context as c, url, request, response
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
from kallithea.lib.utils import jsonify
 

	
 
from kallithea.lib.vcs.exceptions import RepositoryError, \
 
    ChangesetDoesNotExistError
 

	
 
import kallithea.lib.helpers as h
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
 
    NotAnonymous
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.utils import action_logger
 
from kallithea.lib.compat import OrderedDict
 
from kallithea.lib import diffs
 
from kallithea.model.db import ChangesetComment, ChangesetStatus
 
from kallithea.model.comment import ChangesetCommentsModel
 
from kallithea.model.changeset_status import ChangesetStatusModel
 
from kallithea.model.meta import Session
 
from kallithea.model.repo import RepoModel
 
from kallithea.lib.diffs import LimitedDiffContainer
 
from kallithea.lib.exceptions import StatusChangeOnClosedPullRequestError
 
from kallithea.lib.vcs.backends.base import EmptyChangeset
 
from kallithea.lib.utils2 import safe_unicode, safe_str
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _update_with_GET(params, GET):
 
    for k in ['diff1', 'diff2', 'diff']:
 
        params[k] += GET.getall(k)
 

	
 

	
 
def anchor_url(revision, path, GET):
 
    fid = h.FID(revision, path)
 
    return h.url.current(anchor=fid, **dict(GET))
 

	
 

	
 
def get_ignore_ws(fid, GET):
 
    ig_ws_global = GET.get('ignorews')
 
    ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
 
    if ig_ws:
 
        try:
 
            return int(ig_ws[0].split(':')[-1])
 
        except Exception:
 
            pass
 
    return ig_ws_global
 

	
 

	
 
def _ignorews_url(GET, fileid=None):
 
    fileid = str(fileid) if fileid else None
 
    params = defaultdict(list)
 
    _update_with_GET(params, GET)
 
    lbl = _('Show white space')
 
    ig_ws = get_ignore_ws(fileid, GET)
 
    ln_ctx = get_line_ctx(fileid, GET)
 
    # global option
 
    if fileid is None:
 
        if ig_ws is None:
 
            params['ignorews'] += [1]
 
            lbl = _('Ignore white space')
 
        ctx_key = 'context'
 
        ctx_val = ln_ctx
 
    # per file options
 
    else:
 
        if ig_ws is None:
 
            params[fileid] += ['WS:1']
 
            lbl = _('Ignore white space')
 

	
 
        ctx_key = fileid
 
        ctx_val = 'C:%s' % ln_ctx
 
    # if we have passed in ln_ctx pass it along to our params
 
    if ln_ctx:
 
        params[ctx_key] += [ctx_val]
 

	
 
    params['anchor'] = fileid
 
    img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
 
    return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
 

	
 

	
 
def get_line_ctx(fid, GET):
 
    ln_ctx_global = GET.get('context')
 
    if fid:
 
        ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
 
    else:
 
        _ln_ctx = filter(lambda k: k.startswith('C'), GET)
 
        ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx  else ln_ctx_global
 
        if ln_ctx:
 
            ln_ctx = [ln_ctx]
 

	
 
    if ln_ctx:
 
        retval = ln_ctx[0].split(':')[-1]
 
    else:
 
        retval = ln_ctx_global
 

	
 
    try:
 
        return int(retval)
 
    except Exception:
 
        return 3
 

	
 

	
 
def _context_url(GET, fileid=None):
 
    """
 
    Generates url for context lines
 

	
 
    :param fileid:
 
    """
 

	
 
    fileid = str(fileid) if fileid else None
 
    ig_ws = get_ignore_ws(fileid, GET)
 
    ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
 

	
 
    params = defaultdict(list)
 
    _update_with_GET(params, GET)
 

	
 
    # global option
 
    if fileid is None:
 
        if ln_ctx > 0:
 
            params['context'] += [ln_ctx]
 

	
 
        if ig_ws:
 
            ig_ws_key = 'ignorews'
 
            ig_ws_val = 1
 

	
 
    # per file option
 
    else:
 
        params[fileid] += ['C:%s' % ln_ctx]
 
        ig_ws_key = fileid
 
        ig_ws_val = 'WS:%s' % 1
 

	
 
    if ig_ws:
 
        params[ig_ws_key] += [ig_ws_val]
 

	
 
    lbl = _('increase diff context to %(num)s lines') % {'num': ln_ctx}
 

	
 
    params['anchor'] = fileid
 
    img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
 
    return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
 

	
 

	
 
class ChangesetController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(ChangesetController, self).__before__()
 
        c.affected_files_cut_off = 60
 

	
 
    def __load_data(self):
 
        repo_model = RepoModel()
 
        c.users_array = repo_model.get_users_js()
 
        c.user_groups_array = repo_model.get_user_groups_js()
 

	
 
    def _index(self, revision, method):
 
        c.anchor_url = anchor_url
 
        c.ignorews_url = _ignorews_url
 
        c.context_url = _context_url
 
        c.fulldiff = fulldiff = request.GET.get('fulldiff')
 
        #get ranges of revisions if preset
 
        rev_range = revision.split('...')[:2]
 
        enable_comments = True
 
        try:
 
            if len(rev_range) == 2:
 
                enable_comments = False
 
                rev_start = rev_range[0]
 
                rev_end = rev_range[1]
 
                rev_ranges = c.db_repo_scm_instance.get_changesets(start=rev_start,
 
                                                             end=rev_end)
 
            else:
 
                rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)]
 

	
 
            c.cs_ranges = list(rev_ranges)
 
            if not c.cs_ranges:
 
                raise RepositoryError('Changeset range returned empty result')
 

	
 
        except(ChangesetDoesNotExistError,), e:
 
            log.error(traceback.format_exc())
 
            msg = _('Such revision does not exist for this repository')
 
            h.flash(msg, category='error')
 
            raise HTTPNotFound()
 
        except (Exception,), e:
 
            log.error(traceback.format_exc())
 
            raise HTTPNotFound()
 

	
 
        c.changes = OrderedDict()
 

	
 
        c.lines_added = 0  # count of lines added
 
        c.lines_deleted = 0  # count of lines removes
 

	
 
        c.changeset_statuses = ChangesetStatus.STATUSES
 
        c.comments = []
 
        c.statuses = []
 
        c.inline_comments = []
 
        c.inline_cnt = 0
 

	
 
        # Iterate over ranges (default changeset view is always one changeset)
 
        for changeset in c.cs_ranges:
 
            inlines = []
 
            if method == 'show':
 
                c.statuses.extend([ChangesetStatusModel().get_status(
 
                            c.db_repo.repo_id, changeset.raw_id)])
 

	
 
                c.comments.extend(ChangesetCommentsModel()\
 
                                  .get_comments(c.db_repo.repo_id,
 
                                                revision=changeset.raw_id))
 

	
 
                #comments from PR
 
                st = ChangesetStatusModel().get_statuses(
 
                            c.db_repo.repo_id, changeset.raw_id,
 
                            with_revisions=True)
 
                # from associated statuses, check the pull requests, and
 
                # show comments from them
 

	
 
                prs = set([x.pull_request for x in
 
                           filter(lambda x: x.pull_request is not None, st)])
 

	
 
                for pr in prs:
 
                    c.comments.extend(pr.comments)
 
                inlines = ChangesetCommentsModel()\
 
                            .get_inline_comments(c.db_repo.repo_id,
 
                                                 revision=changeset.raw_id)
 
                c.inline_comments.extend(inlines)
 

	
 
            c.changes[changeset.raw_id] = []
 

	
 
            cs2 = changeset.raw_id
 
            cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset().raw_id
 
            context_lcl = get_line_ctx('', request.GET)
 
            ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
 

	
 
            _diff = c.db_repo_scm_instance.get_diff(cs1, cs2,
 
                ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
 
            diff_limit = self.cut_off_limit if not fulldiff else None
 
            diff_processor = diffs.DiffProcessor(_diff,
 
                                                 vcs=c.db_repo_scm_instance.alias,
 
                                                 format='gitdiff',
 
                                                 diff_limit=diff_limit)
 
            cs_changes = OrderedDict()
 
            if method == 'show':
 
                _parsed = diff_processor.prepare()
 
                c.limited_diff = False
 
                if isinstance(_parsed, LimitedDiffContainer):
 
                    c.limited_diff = True
 
                for f in _parsed:
 
                    st = f['stats']
 
                    c.lines_added += st['added']
 
                    c.lines_deleted += st['deleted']
 
                    fid = h.FID(changeset.raw_id, f['filename'])
 
                    diff = diff_processor.as_html(enable_comments=enable_comments,
 
                                                  parsed_lines=[f])
 
                    cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
 
                                       diff, st]
 
            else:
 
                # downloads/raw we only need RAW diff nothing else
 
                diff = diff_processor.as_raw()
 
                cs_changes[''] = [None, None, None, None, diff, None]
 
            c.changes[changeset.raw_id] = cs_changes
 

	
 
        #sort comments by how they were generated
 
        c.comments = sorted(c.comments, key=lambda x: x.comment_id)
 

	
 
        # count inline comments
 
        for __, lines in c.inline_comments:
 
            for comments in lines.values():
 
                c.inline_cnt += len(comments)
 

	
 
        if len(c.cs_ranges) == 1:
 
            c.changeset = c.cs_ranges[0]
 
            c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                     for x in c.changeset.parents])
 
        if method == 'download':
 
            response.content_type = 'text/plain'
 
            response.content_disposition = 'attachment; filename=%s.diff' \
 
                                            % revision[:12]
 
            return diff
 
        elif method == 'patch':
 
            response.content_type = 'text/plain'
 
            c.diff = safe_unicode(diff)
 
            return render('changeset/patch_changeset.html')
 
        elif method == 'raw':
 
            response.content_type = 'text/plain'
 
            return diff
 
        elif method == 'show':
 
            self.__load_data()
 
            if len(c.cs_ranges) == 1:
 
                return render('changeset/changeset.html')
 
            else:
 
                return render('changeset/changeset_range.html')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, revision, method='show'):
 
        return self._index(revision, method=method)
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changeset_raw(self, revision):
 
        return self._index(revision, method='raw')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changeset_patch(self, revision):
 
        return self._index(revision, method='patch')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def changeset_download(self, revision):
 
        return self._index(revision, method='download')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        status = request.POST.get('changeset_status')
 
        change_status = request.POST.get('change_changeset_status')
 
        text = request.POST.get('text')
 
        if status and change_status:
 
            text = text or (_('Status change -> %s')
 
                            % ChangesetStatus.get_status_lbl(status))
 

	
 
        c.co = comm = ChangesetCommentsModel().create(
 
            text=text,
 
            repo=c.db_repo.repo_id,
 
            user=c.authuser.user_id,
 
            revision=revision,
 
            f_path=request.POST.get('f_path'),
 
            line_no=request.POST.get('line'),
 
            status_change=(ChangesetStatus.get_status_lbl(status)
 
                           if status and change_status else None)
 
        )
 

	
 
        # get status if set !
 
        if status and change_status:
 
            # if latest status was from pull request and it's closed
 
            # disallow changing status !
 
            # dont_allow_on_closed_pull_request = True !
 

	
 
            try:
 
                ChangesetStatusModel().set_status(
 
                    c.db_repo.repo_id,
 
                    status,
 
                    c.authuser.user_id,
 
                    comm,
 
                    revision=revision,
 
                    dont_allow_on_closed_pull_request=True
 
                )
 
            except StatusChangeOnClosedPullRequestError:
 
                log.error(traceback.format_exc())
 
                msg = _('Changing status on a changeset associated with '
 
                        'a closed pull request is not allowed')
 
                h.flash(msg, category='warning')
 
                return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                      revision=revision))
 
        action_logger(self.authuser,
 
                      'user_commented_revision:%s' % revision,
 
                      c.db_repo, self.ip_addr, self.sa)
 

	
 
        Session().commit()
 

	
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return redirect(h.url('changeset_home', repo_name=repo_name,
 
                                  revision=revision))
 
        #only ajax below
 
        data = {
 
           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
        }
 
        if comm:
 
            data.update(comm.get_dict())
 
            data.update({'rendered_text':
 
                         render('changeset/changeset_comment_block.html')})
 

	
 
        return data
kallithea/controllers/compare.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.controllers.compare
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
compare controller for pylons showing differences between two
 
repos, branches, bookmarks or tips
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: May 6, 2012
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH.
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
import logging
 
import traceback
 
import re
 

	
 
from webob.exc import HTTPNotFound, HTTPBadRequest
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from kallithea.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
 
from kallithea.lib.vcs.utils import safe_str
 
from kallithea.lib.vcs.utils.hgcompat import unionrepo
 
from kallithea.lib import helpers as h
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from kallithea.lib import diffs
 
from kallithea.model.db import Repository
 
from kallithea.lib.diffs import LimitedDiffContainer
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class CompareController(BaseRepoController):
 

	
 
    def __before__(self):
 
        super(CompareController, self).__before__()
 

	
 
    def __get_rev_or_redirect(self, ref, repo, redirect_after=True,
 
                              partial=False):
 
        """
 
        Safe way to get changeset if error occur it redirects to changeset with
 
        proper message. If partial is set then don't do redirect raise Exception
 
        instead
 

	
 
        :param ref:
 
        :param repo:
 
        :param redirect_after:
 
        :param partial:
 
        """
 
        rev = ref[1] # default and used for git
 
        if repo.scm_instance.alias == 'hg':
 
            # lookup up the exact node id
 
            _revset_predicates = {
 
                    'branch': 'branch',
 
                    'book': 'bookmark',
 
                    'tag': 'tag',
 
                    'rev': 'id',
 
                }
 
            rev_spec = "max(%s(%%s))" % _revset_predicates[ref[0]]
 
            revs = repo.scm_instance._repo.revs(rev_spec, safe_str(ref[1]))
 
            if revs:
 
                rev = revs[-1]
 
            # else: TODO: just report 'not found'
 

	
 
        try:
 
            return repo.scm_instance.get_changeset(rev).raw_id
 
        except EmptyRepositoryError, e:
 
            if not redirect_after:
 
                return None
 
            h.flash(h.literal(_('There are no changesets yet')),
 
                    category='warning')
 
            redirect(url('summary_home', repo_name=repo.repo_name))
 

	
 
        except RepositoryError, e:
 
            log.error(traceback.format_exc())
 
            h.flash(safe_str(e), category='warning')
 
            if not partial:
 
                redirect(h.url('summary_home', repo_name=repo.repo_name))
 
            raise HTTPBadRequest()
 

	
 
    def _get_changesets(self, alias, org_repo, org_rev, other_repo, other_rev, merge):
 
        """
 
        Returns a list of changesets that can be merged from org_repo at org_rev
 
        to other_repo at other_rev ... and the ancestor that would be used for merge.
 

	
 
        :param org_repo: repo object, that is most likely the orginal repo we forked from
 
        :param org_rev: the revision we want our compare to be made
 
        :param other_repo: repo object, mostl likely the fork of org_repo. It hass
 
            all changesets that we need to obtain
 
        :param other_rev: revision we want out compare to be made on other_repo
 

	
 
        """
 
        ancestor = None
 
        if org_rev == other_rev:
 
            changesets = []
 
            if merge:
 
                ancestor = org_rev
 

	
 
        elif alias == 'hg':
 
            #case two independent repos
 
            if org_repo != other_repo:
 
                hgrepo = unionrepo.unionrepository(other_repo.baseui,
 
                                                   other_repo.path,
 
                                                   org_repo.path)
 
                # all the changesets we are looking for will be in other_repo,
 
                # so rev numbers from hgrepo can be used in other_repo
 

	
 
            #no remote compare do it on the same repository
 
            else:
 
                hgrepo = other_repo._repo
 

	
 
            if merge:
 
                revs = hgrepo.revs(
 
                    "ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
 
                    other_rev, org_rev, org_rev)
 

	
 
                ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev,
 
                                        other_rev)
 
                if ancestors:
 
                    # pick arbitrary ancestor - but there is usually only one
 
                    ancestor = hgrepo[ancestors[0]].hex()
 
            else:
 
                # TODO: have both + and - changesets
 
                revs = hgrepo.revs("id(%s) :: id(%s) - id(%s)",
 
                                   org_rev, other_rev, org_rev)
 

	
 
            changesets = [other_repo.get_changeset(rev) for rev in revs]
 

	
 
        elif alias == 'git':
 
            if org_repo != other_repo:
 
                from dulwich.repo import Repo
 
                from dulwich.client import SubprocessGitClient
 

	
 
                gitrepo = Repo(org_repo.path)
 
                SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo)
 

	
 
                gitrepo_remote = Repo(other_repo.path)
 
                SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote)
 

	
 
                revs = []
 
                for x in gitrepo_remote.get_walker(include=[other_rev],
 
                                                   exclude=[org_rev]):
 
                    revs.append(x.commit.id)
 

	
 
                changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
 
                if changesets:
 
                    ancestor = changesets[0].parents[0].raw_id
 
                else:
 
                    # no changesets from other repo, ancestor is the other_rev
 
                    ancestor = other_rev
 

	
 
            else:
 
                so, se = org_repo.run_git_command(
 
                    'log --reverse --pretty="format: %%H" -s %s..%s'
 
                        % (org_rev, other_rev)
 
                )
 
                changesets = [org_repo.get_changeset(cs)
 
                              for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
 

	
 
        else:
 
            raise Exception('Bad alias only git and hg is allowed')
 

	
 
        return changesets, ancestor
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, repo_name):
 
        c.compare_home = True
 
        org_repo = c.db_repo.repo_name
 
        other_repo = request.GET.get('other_repo', org_repo)
 
        c.org_repo = Repository.get_by_repo_name(org_repo)
 
        c.other_repo = Repository.get_by_repo_name(other_repo)
 
        c.org_ref = c.other_ref = _('Select changeset')
 
        return render('compare/compare_diff.html')
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def compare(self, repo_name, org_ref_type, org_ref, other_ref_type, other_ref):
 
        # org_ref will be evaluated in org_repo
 
        org_repo = c.db_repo.repo_name
 
        org_ref = (org_ref_type, org_ref)
 
        # other_ref will be evaluated in other_repo
 
        other_ref = (other_ref_type, other_ref)
 
        other_repo = request.GET.get('other_repo', org_repo)
 
        # If merge is True:
 
        #   Show what org would get if merged with other:
 
        #   List changesets that are ancestors of other but not of org.
 
        #   New changesets in org is thus ignored.
 
        #   Diff will be from common ancestor, and merges of org to other will thus be ignored.
 
        # If merge is False:
 
        #   Make a raw diff from org to other, no matter if related or not.
 
        #   Changesets in one and not in the other will be ignored
 
        merge = bool(request.GET.get('merge'))
 
        # fulldiff disables cut_off_limit
 
        c.fulldiff = request.GET.get('fulldiff')
 
        # partial uses compare_cs.html template directly
 
        partial = request.environ.get('HTTP_X_PARTIAL_XHR')
 
        # as_form puts hidden input field with changeset revisions
 
        c.as_form = partial and request.GET.get('as_form')
 
        # swap url for compare_diff page - never partial and never as_form
 
        c.swap_url = h.url('compare_url',
 
            repo_name=other_repo,
 
            org_ref_type=other_ref[0], org_ref=other_ref[1],
 
            other_repo=org_repo,
 
            other_ref_type=org_ref[0], other_ref=org_ref[1],
 
            merge=merge or '')
 

	
 
        org_repo = Repository.get_by_repo_name(org_repo)
 
        other_repo = Repository.get_by_repo_name(other_repo)
 

	
 
        if org_repo is None:
 
            msg = 'Could not find org repo %s' % org_repo
 
            log.error(msg)
 
            h.flash(msg, category='error')
 
            return redirect(url('compare_home', repo_name=c.repo_name))
 

	
 
        if other_repo is None:
 
            msg = 'Could not find other repo %s' % other_repo
 
            log.error(msg)
 
            h.flash(msg, category='error')
 
            return redirect(url('compare_home', repo_name=c.repo_name))
 

	
 
        if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
 
            msg = 'compare of two different kind of remote repos not available'
 
            log.error(msg)
 
            h.flash(msg, category='error')
 
            return redirect(url('compare_home', repo_name=c.repo_name))
 

	
 
        org_rev = self.__get_rev_or_redirect(ref=org_ref, repo=org_repo, partial=partial)
 
        other_rev = self.__get_rev_or_redirect(ref=other_ref, repo=other_repo, partial=partial)
 

	
 
        c.compare_home = False
 
        c.org_repo = org_repo
 
        c.other_repo = other_repo
 
        c.org_ref = org_ref[1]
 
        c.other_ref = other_ref[1]
 
        c.org_ref_type = org_ref[0]
 
        c.other_ref_type = other_ref[0]
 

	
 
        c.cs_ranges, c.ancestor = self._get_changesets(
 
            org_repo.scm_instance.alias, org_repo.scm_instance, org_rev,
 
            other_repo.scm_instance, other_rev, merge)
 
        c.statuses = c.db_repo.statuses(
 
            [x.raw_id for x in c.cs_ranges])
 

	
 
        if merge and not c.ancestor:
 
            log.error('Unable to find ancestor revision')
 

	
 
        if partial:
 
            return render('compare/compare_cs.html')
 
        if c.ancestor:
 
            assert merge
 
            # case we want a simple diff without incoming changesets,
 
            # previewing what will be merged.
 
            # Make the diff on the other repo (which is known to have other_ref)
 
            log.debug('Using ancestor %s as org_ref instead of %s'
 
                      % (c.ancestor, org_ref))
 
            org_rev = c.ancestor
 
            org_repo = other_repo
 

	
 
        diff_limit = self.cut_off_limit if not c.fulldiff else None
 

	
 
        log.debug('running diff between %s and %s in %s'
 
                  % (org_rev, other_rev, org_repo.scm_instance.path))
 

	
 
        txtdiff = org_repo.scm_instance.get_diff(rev1=org_rev, rev2=other_rev)
 
        diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
 
                                             diff_limit=diff_limit)
 
        _parsed = diff_processor.prepare()
 

	
 
        c.limited_diff = False
 
        if isinstance(_parsed, LimitedDiffContainer):
 
            c.limited_diff = True
 

	
 
        c.files = []
 
        c.changes = {}
 
        c.lines_added = 0
 
        c.lines_deleted = 0
 
        for f in _parsed:
 
            st = f['stats']
 
            if not st['binary']:
 
                c.lines_added += st['added']
 
                c.lines_deleted += st['deleted']
 
            fid = h.FID('', f['filename'])
 
            c.files.append([fid, f['operation'], f['filename'], f['stats']])
 
            htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
 
            c.changes[fid] = [f['operation'], f['filename'], htmldiff]
 

	
 
        return render('compare/compare_diff.html')

Changeset was too big and was cut off... Show full diff anyway

0 comments (0 inline, 0 general)